export.cljs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. (ns frontend.handler.export
  2. (:require [cljs-bean.core :as bean]
  3. [cljs.pprint :as pprint]
  4. [clojure.set :as s]
  5. [clojure.string :as string]
  6. [clojure.walk :as walk]
  7. [datascript.core :as d]
  8. [frontend.config :as config]
  9. [frontend.db :as db]
  10. [frontend.extensions.zip :as zip]
  11. [frontend.external.roam-export :as roam-export]
  12. [frontend.format :as f]
  13. [frontend.format.protocol :as fp]
  14. [frontend.handler.common :as common-handler]
  15. [frontend.handler.file :as file-handler]
  16. [frontend.modules.file.core :as outliner-file]
  17. [frontend.modules.outliner.tree :as outliner-tree]
  18. [frontend.publishing.html :as html]
  19. [frontend.state :as state]
  20. [frontend.util :as util]
  21. [goog.dom :as gdom]
  22. [promesa.core :as p]))
  23. (defn- get-page-content
  24. [page]
  25. (outliner-file/tree->file-content
  26. (outliner-tree/blocks->vec-tree
  27. (db/get-page-blocks-no-cache page) page) {:init-level 1}))
  28. (defn- get-page-content-debug
  29. [page]
  30. (outliner-file/tree->file-content
  31. (outliner-tree/blocks->vec-tree
  32. (db/get-page-blocks-no-cache page) page) {:init-level 1
  33. :heading-to-list? true}))
  34. (defn- get-file-content
  35. [file-path]
  36. (if-let [page-name
  37. (ffirst (d/q '[:find ?pn
  38. :in $ ?path
  39. :where
  40. [?p :block/file ?f]
  41. [?p :block/name ?pn]
  42. [?f :file/path ?path]]
  43. (db/get-conn) file-path))]
  44. (get-page-content page-name)
  45. (ffirst
  46. (d/q '[:find ?content
  47. :in $ ?path
  48. :where
  49. [?f :file/path ?path]
  50. [?f :file/content ?content]]
  51. (db/get-conn) file-path))))
  52. (defn- get-blocks-contents
  53. [repo root-block-uuid]
  54. (->
  55. (db/get-block-and-children repo root-block-uuid)
  56. (outliner-tree/blocks->vec-tree (str root-block-uuid))
  57. (outliner-file/tree->file-content {:init-level 1})))
  58. (defn- get-block-content
  59. [repo block]
  60. (->
  61. [block]
  62. (outliner-tree/blocks->vec-tree (str (:block/uuid block)))
  63. (outliner-file/tree->file-content {:init-level 1})))
  64. (defn export-repo-as-json!
  65. [repo]
  66. (when-let [db (db/get-conn repo)]
  67. (let [db-json (db/db->json db)
  68. data-str (str "data:text/json;charset=utf-8," (js/encodeURIComponent db-json))]
  69. (when-let [anchor (gdom/getElement "download-as-json")]
  70. (.setAttribute anchor "href" data-str)
  71. (.setAttribute anchor "download" (str (last (string/split repo #"/")) ".json"))
  72. (.click anchor)))))
  73. (defn download-file!
  74. [file-path]
  75. (when-let [content (get-file-content file-path)]
  76. (let [data (js/Blob. ["\ufeff" (array content)] ; prepend BOM
  77. (clj->js {:type "text/plain;charset=utf-8,"}))]
  78. (let [anchor (gdom/getElement "download")
  79. url (js/window.URL.createObjectURL data)]
  80. (.setAttribute anchor "href" url)
  81. (.setAttribute anchor "download" file-path)
  82. (.click anchor)))))
  83. (defn export-repo-as-html!
  84. [repo]
  85. (when-let [db (db/get-conn repo)]
  86. (let [[db asset-filenames] (if (state/all-pages-public?)
  87. (db/clean-export! db)
  88. (db/filter-only-public-pages-and-blocks db))
  89. db-str (db/db->string db)
  90. state (select-keys @state/state
  91. [:ui/theme :ui/cycle-collapse
  92. :ui/sidebar-collapsed-blocks
  93. :ui/show-recent?
  94. :config])
  95. state (update state :config (fn [config]
  96. {"local" (get config repo)}))
  97. raw-html-str (html/publishing-html db-str (pr-str state))
  98. html-str (str "data:text/html;charset=UTF-8,"
  99. (js/encodeURIComponent raw-html-str))]
  100. (if (util/electron?)
  101. (js/window.apis.exportPublishAssets raw-html-str (config/get-custom-css-path) (config/get-repo-dir repo) (clj->js asset-filenames))
  102. (when-let [anchor (gdom/getElement "download-as-html")]
  103. (.setAttribute anchor "href" html-str)
  104. (.setAttribute anchor "download" "index.html")
  105. (.click anchor))))))
  106. (defn- get-file-contents
  107. ([repo]
  108. (get-file-contents repo {:init-level 1}))
  109. ([repo file-opts]
  110. (let [conn (db/get-conn repo)]
  111. (->> (d/q '[:find ?n ?fp
  112. :where
  113. [?e :block/file ?f]
  114. [?f :file/path ?fp]
  115. [?e :block/name ?n]] conn)
  116. (mapv (fn [[page-name file-path]]
  117. [file-path
  118. (outliner-file/tree->file-content
  119. (outliner-tree/blocks->vec-tree
  120. (db/get-page-blocks-no-cache page-name) page-name)
  121. file-opts)]))))))
  122. (defn export-repo-as-zip!
  123. [repo]
  124. (let [files (get-file-contents repo)
  125. [owner repo-name] (util/get-git-owner-and-repo repo)
  126. repo-name (str owner "-" repo-name)]
  127. (when (seq files)
  128. (p/let [zipfile (zip/make-zip repo-name files repo)]
  129. (when-let [anchor (gdom/getElement "download")]
  130. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  131. (.setAttribute anchor "download" (.-name zipfile))
  132. (.click anchor))))))
  133. (defn export-git-repo-as-zip!
  134. [repo]
  135. (p/let [files (file-handler/load-files repo)
  136. contents (file-handler/load-multiple-files repo files)
  137. files (zipmap files contents)
  138. [owner repo-name] (util/get-git-owner-and-repo repo)
  139. repo-name (str owner "-" repo-name)]
  140. (when (seq files)
  141. (p/let [zipfile (zip/make-zip repo-name files repo)]
  142. (when-let [anchor (gdom/getElement "download")]
  143. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  144. (.setAttribute anchor "download" (.-name zipfile))
  145. (.click anchor))))))
  146. (defn get-md-file-contents
  147. [repo]
  148. (let [conn (db/get-conn repo)]
  149. (filter (fn [[path _]]
  150. (let [path (string/lower-case path)]
  151. (re-find #"\.(?:md|markdown)$" path)))
  152. (get-file-contents repo {:init-level 1
  153. :heading-to-list? true}))))
  154. (defn- get-embed-and-refs-blocks-pages-aux []
  155. (let [mem (atom {})]
  156. (letfn [(f [repo page-or-block is-block? exclude-blocks exclude-pages ttl]
  157. (let [v (get @mem [repo page-or-block])]
  158. (if v v
  159. (let [[ref-blocks ref-pages]
  160. (->> (if is-block?
  161. (if (or (seq? page-or-block)
  162. (vector? page-or-block))
  163. (vec page-or-block)
  164. [page-or-block])
  165. (db/get-page-blocks-no-cache
  166. repo page-or-block {:pull-keys '[:block/refs]}))
  167. (filterv :block/refs)
  168. (mapcat :block/refs)
  169. (group-by #(boolean (:block/page (db/entity (:db/id %)))))
  170. ((fn [g] [(get g true []) (get g false [])]))
  171. (mapv #(vec (distinct (flatten (remove nil? %))))))
  172. ref-block-ids
  173. (->> ref-blocks
  174. (#(remove (fn [b] (contains? exclude-blocks (:db/id b))) %))
  175. (mapv #(:db/id %)))
  176. ref-page-ids
  177. (->> ref-pages
  178. (#(remove (fn [b] (contains? exclude-pages (:db/id b))) %))
  179. (mapv #(:db/id %)))
  180. ref-blocks
  181. (->> ref-block-ids
  182. (db/pull-many repo '[*])
  183. (flatten))
  184. ref-pages
  185. (->> ref-page-ids
  186. (db/pull-many repo '[*])
  187. (filterv :block/name)
  188. (flatten))
  189. [next-ref-blocks1 next-ref-pages1]
  190. (if (<= ttl 0) [[] []]
  191. (->> ref-blocks
  192. (mapv #(f repo % true (set (concat ref-block-ids exclude-blocks)) exclude-pages (- ttl 1)))
  193. (apply mapv vector)))
  194. [next-ref-blocks2 next-ref-pages2]
  195. (if (<= ttl 0) [[] []]
  196. (->> ref-pages
  197. (mapv #(f repo (:block/name %) false exclude-blocks (set (concat ref-page-ids exclude-pages)) (- ttl 1)))
  198. (apply mapv vector)))
  199. result
  200. [(->> (concat ref-block-ids next-ref-blocks1 next-ref-blocks2)
  201. (flatten)
  202. (distinct))
  203. (->> (concat ref-page-ids next-ref-pages1 next-ref-pages2)
  204. (flatten)
  205. (distinct))]]
  206. (when (and (empty? exclude-blocks) (empty? exclude-pages))
  207. (swap! mem assoc [repo page-or-block] result))
  208. result))))]
  209. f)))
  210. (defn- get-page&block-refs-by-query
  211. [repo page-or-block get-page&block-refs-by-query-aux {:keys [is-block?] :or {is-block? false}}]
  212. (let [[block-ids page-ids]
  213. (get-page&block-refs-by-query-aux repo page-or-block is-block? #{} #{} 3)
  214. blocks
  215. (db/pull-many repo '[*] block-ids)
  216. pages-name-and-content
  217. (->> page-ids
  218. (d/q '[:find ?n ?n2 (pull ?p [:file/path])
  219. :in $ [?e ...]
  220. :where
  221. [?e :block/file ?p]
  222. [?e :block/name ?n]
  223. [?e :block/original-name ?n2]] (db/get-conn repo))
  224. (mapv (fn [[name origin-name file-path]]
  225. (if (= name origin-name)
  226. [[name file-path]]
  227. [[name file-path] [origin-name file-path]])))
  228. (apply concat)
  229. (mapv (fn [[page-name file-path]]
  230. [page-name (get-page-content page-name)])))
  231. embed-blocks
  232. (mapv (fn [b] [(str (:block/uuid b))
  233. [(get-blocks-contents repo (:block/uuid b))
  234. (get-block-content repo b)]])
  235. blocks)]
  236. {:embed_blocks embed-blocks
  237. :embed_pages pages-name-and-content}))
  238. (defn- page&block-refs
  239. [repo]
  240. (let [block-refs
  241. (->>
  242. (d/q '[:find ?pn ?pon ?bt ?bc ?bid ?e ?rb
  243. :where
  244. [?e :block/refs ?rb]
  245. [?e :block/page ?p]
  246. [?p :block/name ?pn]
  247. [?p :block/original-name ?pon]
  248. [?rb :block/title ?bt]
  249. [?rb :block/content ?bc]
  250. [?rb :block/uuid ?bid]] (db/get-conn repo)))
  251. page-block-refs
  252. (->> block-refs
  253. (mapv (fn [[pn pon bt bc bid _ rb]]
  254. (if (= pn pon)
  255. [[pn bt bc bid rb]]
  256. [[pn bt bc bid rb] [pon bt bc bid rb]])))
  257. (apply concat)
  258. (reduce (fn [r [k & v]] (assoc r k (cons v (get r k)))) {})
  259. (mapv (fn [[k v]] [k (distinct v)]))
  260. (into {}))
  261. block-block-refs
  262. (->> block-refs
  263. (mapv (fn [[_ _ bt bc bid e rb]] [e bt bc bid rb]))
  264. (reduce (fn [r [k & v]] (assoc r k (cons v (get r k)))) {})
  265. (mapv (fn [[k v]] [k (distinct v)]))
  266. (into {}))
  267. page-refs
  268. (->> (d/q '[:find ?pn ?pon ?rpn ?rpon ?fp ?e
  269. :where
  270. [?e :block/refs ?rp]
  271. [?e :block/page ?p]
  272. [?p :block/name ?pn]
  273. [?p :block/original-name ?pon]
  274. [?rp :block/name ?rpn]
  275. [?rp :block/original-name ?rpon]
  276. [?rp :block/file ?pf]
  277. [?pf :file/path ?fp]] (db/get-conn repo))
  278. (d/q '[:find ?pn ?pon ?rpn ?rpon ?fc ?be
  279. :in $ [[?pn ?pon ?rpn ?rpon ?fp ?be] ...]
  280. :where
  281. [?e :file/path ?fp]
  282. [?e :file/content ?fc]] (db/get-conn repo)))
  283. page-page-refs
  284. (->>
  285. page-refs
  286. (mapv (fn [[pn pon rpn rpon fc _]]
  287. (case [(= pn pon) (= rpn rpon)]
  288. [true true] [[pn rpn fc]]
  289. [true false] [[pn rpn fc] [pn rpon fc]]
  290. [false true] [[pn rpn fc] [pon rpn fc]]
  291. [false false] [[pn rpn fc] [pn rpon fc] [pon rpn fc] [pon rpon fc]])))
  292. (apply concat)
  293. (reduce (fn [r [k & v]] (assoc r k (cons v (get r k)))) {})
  294. (mapv (fn [[k v]] [k (distinct v)]))
  295. (into {}))
  296. block-page-refs
  297. (->>
  298. page-refs
  299. (mapv (fn [[pn pon rpn rpon fc e]]
  300. (if (= rpn rpon) [[e rpn fc]] [[e rpn fc] [e rpon fc]])))
  301. (apply concat)
  302. (reduce (fn [r [k & v]] (assoc r k (cons v (get r k)))) {})
  303. (mapv (fn [[k v]] [k (distinct v)]))
  304. (into {}))]
  305. [page-block-refs page-page-refs block-block-refs block-page-refs]))
  306. (defn- get-page&block-refs-aux
  307. [repo page-or-block-id is-block-id? page&block-refs exclude-blocks exclude-pages]
  308. (let [[page-block-refs page-page-refs block-block-refs block-page-refs] page&block-refs]
  309. (if is-block-id?
  310. (when (not (contains? exclude-blocks page-or-block-id))
  311. (let [block-refs (get block-block-refs page-or-block-id)
  312. block-ref-ids (->>
  313. (mapv (fn [[_ _ _ rb]] rb) block-refs)
  314. (remove #(contains? exclude-blocks %)))
  315. page-refs (get block-page-refs page-or-block-id)
  316. page-ref-names (->>
  317. (mapv (fn [[rpn _]] rpn) page-refs)
  318. (remove #(contains? exclude-pages %)))
  319. [other-block-refs1 other-page-refs1]
  320. (->>
  321. (mapv
  322. #(get-page&block-refs-aux repo % true
  323. page&block-refs
  324. (conj exclude-blocks %)
  325. exclude-pages)
  326. block-ref-ids)
  327. (apply mapv vector))
  328. [other-block-refs2 other-page-refs2]
  329. (->>
  330. (mapv
  331. #(get-page&block-refs-aux repo % false
  332. page&block-refs
  333. exclude-blocks
  334. (conj exclude-pages %))
  335. page-ref-names)
  336. (apply mapv vector))
  337. block-refs* (apply concat (concat other-block-refs1 other-block-refs2 [block-refs]))
  338. page-refs* (apply concat (concat other-page-refs1 other-page-refs2 [page-refs]))]
  339. [block-refs* page-refs*]))
  340. (when (not (contains? exclude-pages page-or-block-id))
  341. (let [block-refs (get page-block-refs page-or-block-id)
  342. block-ref-ids (->>
  343. (mapv (fn [[_ _ _ rb]] rb) block-refs)
  344. (remove #(contains? exclude-blocks %)))
  345. page-refs (get page-page-refs page-or-block-id)
  346. page-ref-names (->>
  347. (mapv (fn [[rpn _]] rpn) page-refs)
  348. (remove #(contains? exclude-pages %)))
  349. [other-block-refs1 other-page-refs1]
  350. (->>
  351. (mapv
  352. #(get-page&block-refs-aux repo % true
  353. page&block-refs
  354. (conj exclude-blocks %)
  355. exclude-pages)
  356. block-ref-ids)
  357. (apply mapv vector))
  358. [other-block-refs2 other-page-refs2]
  359. (->>
  360. (mapv
  361. #(get-page&block-refs-aux repo % false
  362. page&block-refs
  363. exclude-blocks
  364. (conj exclude-pages %))
  365. page-ref-names)
  366. (apply mapv vector))
  367. block-refs* (apply concat (concat other-block-refs1 other-block-refs2 [block-refs]))
  368. page-refs* (apply concat (concat other-page-refs1 other-page-refs2 [page-refs]))]
  369. [block-refs* page-refs*])))))
  370. (defn- get-page&block-refs
  371. [repo page page&block-refs]
  372. (let [[block-refs page-refs]
  373. (get-page&block-refs-aux repo page false page&block-refs #{} #{})]
  374. {:embed_blocks
  375. (mapv (fn [[_title content uuid id]]
  376. [(str uuid)
  377. [(get-blocks-contents repo uuid)
  378. (get-block-content repo (db/pull id))]])
  379. block-refs)
  380. :embed_pages (vec page-refs)}))
  381. (defn- export-files-as-markdown
  382. [repo files heading-to-list?]
  383. (let [get-page&block-refs-by-query-aux (get-embed-and-refs-blocks-pages-aux)
  384. f (if (< (count files) 30) ;query db for per page if (< (count files) 30), or pre-compute whole graph's page&block-refs
  385. #(get-page&block-refs-by-query repo % get-page&block-refs-by-query-aux {})
  386. (let [page&block-refs (page&block-refs repo)]
  387. #(get-page&block-refs repo % page&block-refs)))]
  388. (->> files
  389. (mapv (fn [{:keys [path content names format]}]
  390. (when (first names)
  391. [path (fp/exportMarkdown f/mldoc-record content
  392. (f/get-default-config format {:export-heading-to-list? heading-to-list?})
  393. (js/JSON.stringify
  394. (clj->js (f (first names)))))])))
  395. (remove nil?))))
  396. (defn- export-files-as-opml
  397. [repo files]
  398. (let [get-page&block-refs-by-query-aux (get-embed-and-refs-blocks-pages-aux)
  399. f (if (< (count files) 30) ;query db for per page if (< (count files) 30), or pre-compute whole graph's page&block-refs
  400. #(get-page&block-refs-by-query repo % get-page&block-refs-by-query-aux {})
  401. (let [page&block-refs (page&block-refs repo)]
  402. #(get-page&block-refs repo % page&block-refs)))]
  403. (->> files
  404. (mapv (fn [{:keys [path content names format]}]
  405. (when (first names)
  406. (let [path
  407. (string/replace
  408. (string/lower-case path) #"(.+)\.(md|markdown|org)" "$1.opml")]
  409. [path (fp/exportOPML f/mldoc-record content
  410. (f/get-default-config format)
  411. (first names)
  412. (js/JSON.stringify
  413. (clj->js (f (first names)))))]))))
  414. (remove nil?))))
  415. (defn export-blocks-as-opml
  416. [repo root-block-uuid]
  417. (let [get-page&block-refs-by-query-aux (get-embed-and-refs-blocks-pages-aux)
  418. f #(get-page&block-refs-by-query repo % get-page&block-refs-by-query-aux {:is-block? true})
  419. root-block (db/entity [:block/uuid root-block-uuid])
  420. blocks (db/get-block-and-children repo root-block-uuid)
  421. refs (f blocks)
  422. content (get-blocks-contents repo root-block-uuid)
  423. format (or (:block/format root-block) (state/get-preferred-format))]
  424. (fp/exportOPML f/mldoc-record content
  425. (f/get-default-config format)
  426. "untitled"
  427. (js/JSON.stringify (clj->js refs)))))
  428. (defn export-blocks-as-markdown
  429. [repo root-block-uuid indent-style]
  430. (let [get-page&block-refs-by-query-aux (get-embed-and-refs-blocks-pages-aux)
  431. f #(get-page&block-refs-by-query repo % get-page&block-refs-by-query-aux {:is-block? true})
  432. root-block (db/entity [:block/uuid root-block-uuid])
  433. blocks (db/get-block-and-children repo root-block-uuid)
  434. refs (f blocks)
  435. content (get-blocks-contents repo root-block-uuid)
  436. format (or (:block/format root-block) (state/get-preferred-format))]
  437. (fp/exportMarkdown f/mldoc-record content
  438. (f/get-default-config format {:export-md-indent-style indent-style})
  439. (js/JSON.stringify (clj->js refs)))))
  440. (defn export-blocks-as-html
  441. [repo root-block-uuid]
  442. (let [get-page&block-refs-by-query-aux (get-embed-and-refs-blocks-pages-aux)
  443. f #(get-page&block-refs-by-query repo % get-page&block-refs-by-query-aux {:is-block? true})
  444. root-block (db/entity [:block/uuid root-block-uuid])
  445. blocks (db/get-block-and-children repo root-block-uuid)
  446. refs (f blocks)
  447. content (get-blocks-contents repo root-block-uuid)
  448. format (or (:block/format root-block) (state/get-preferred-format))]
  449. (fp/toHtml f/mldoc-record content
  450. (f/get-default-config format)
  451. (js/JSON.stringify (clj->js refs)))))
  452. (defn- convert-md-files-unordered-list-or-heading
  453. [repo files heading-to-list?]
  454. (->> files
  455. (mapv (fn [{:keys [path content names format]}]
  456. (when (first names)
  457. [path (fp/exportMarkdown f/mldoc-record content
  458. (f/get-default-config format {:export-heading-to-list? heading-to-list? :export-keep-properties? true})
  459. nil)])))
  460. (remove nil?)))
  461. (defn- get-file-contents-with-suffix
  462. [repo]
  463. (let [conn (db/get-conn repo)
  464. md-files (get-md-file-contents repo)]
  465. (->>
  466. md-files
  467. (map (fn [[path content]] {:path path :content content
  468. :names (d/q '[:find [?n ?n2]
  469. :in $ ?p
  470. :where [?e :file/path ?p]
  471. [?e2 :block/file ?e]
  472. [?e2 :block/name ?n]
  473. [?e2 :block/original-name ?n2]] conn path)
  474. :format (f/get-format path)})))))
  475. (defn export-repo-as-markdown!
  476. [repo]
  477. (when-let [repo (state/get-current-repo)]
  478. (when-let [files (get-file-contents-with-suffix repo)]
  479. (let [heading-to-list? (state/export-heading-to-list?)
  480. files
  481. (export-files-as-markdown repo files heading-to-list?)
  482. zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
  483. (p/let [zipfile (zip/make-zip zip-file-name files repo)]
  484. (when-let [anchor (gdom/getElement "export-as-markdown")]
  485. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  486. (.setAttribute anchor "download" (.-name zipfile))
  487. (.click anchor)))))))
  488. (defn export-page-as-markdown!
  489. [page-name]
  490. (when-let [repo (state/get-current-repo)]
  491. (when-let [file (db/get-page-file page-name)]
  492. (when-let [path (:file/path file)]
  493. (when-let [content (get-page-content page-name)]
  494. (let [names [page-name]
  495. format (f/get-format path)
  496. files [{:path path :content content :names names :format format}]]
  497. (let [files
  498. (export-files-as-markdown repo files (state/export-heading-to-list?))]
  499. (let [data (js/Blob. [(second (first files))]
  500. (clj->js {:type "text/plain;charset=utf-8,"}))]
  501. (let [anchor (gdom/getElement "export-page-as-markdown")
  502. url (js/window.URL.createObjectURL data)]
  503. (.setAttribute anchor "href" url)
  504. (.setAttribute anchor "download" path)
  505. (.click anchor))))))))))
  506. (defn export-repo-as-opml!
  507. [repo]
  508. (when-let [repo (state/get-current-repo)]
  509. (when-let [files (get-file-contents-with-suffix repo)]
  510. (let [files (export-files-as-opml repo files)
  511. zip-file-name (str repo "_opml_" (quot (util/time-ms) 1000))]
  512. (p/let [zipfile (zip/make-zip zip-file-name files repo)]
  513. (when-let [anchor (gdom/getElement "export-as-opml")]
  514. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  515. (.setAttribute anchor "download" (.-name zipfile))
  516. (.click anchor)))))))
  517. (defn export-page-as-opml!
  518. [page-name]
  519. (when-let [repo (state/get-current-repo)]
  520. (when-let [file (db/get-page-file page-name)]
  521. (when-let [path (:file/path file)]
  522. (when-let [content (get-page-content page-name)]
  523. (let [names [page-name]
  524. format (f/get-format path)
  525. files [{:path path :content content :names names :format format}]]
  526. (let [files (export-files-as-opml repo files)]
  527. (let [data (js/Blob. [(second (first files))]
  528. (clj->js {:type "text/plain;charset=utf-8,"}))]
  529. (let [anchor (gdom/getElement "export-page-as-opml")
  530. url (js/window.URL.createObjectURL data)
  531. opml-path (string/replace (string/lower-case path) #"(.+)\.(md|org|markdown)$" "$1.opml")]
  532. (.setAttribute anchor "href" url)
  533. (.setAttribute anchor "download" opml-path)
  534. (.click anchor))))))))))
  535. (defn convert-page-markdown-unordered-list-or-heading!
  536. [page-name]
  537. (when-let [repo (state/get-current-repo)]
  538. (when-let [file (db/get-page-file page-name)]
  539. (when-let [path (:file/path file)]
  540. (when-let [content (get-page-content page-name)]
  541. (let [names [page-name]
  542. format (f/get-format path)
  543. files [{:path path :content content :names names :format format}]]
  544. (let [files (convert-md-files-unordered-list-or-heading repo files (state/export-heading-to-list?))]
  545. (let [data (js/Blob. [(second (first files))]
  546. (clj->js {:type "text/plain;charset=utf-8,"}))]
  547. (let [anchor (gdom/getElement "convert-markdown-to-unordered-list-or-heading")
  548. url (js/window.URL.createObjectURL data)]
  549. (.setAttribute anchor "href" url)
  550. (.setAttribute anchor "download" path)
  551. (.click anchor))))))))))
  552. (defn- dissoc-properties [m ks]
  553. (if (:block/properties m)
  554. (update m :block/properties
  555. (fn [v]
  556. (apply dissoc v ks)))
  557. m))
  558. (defn- nested-select-keys
  559. [keyseq vec-tree]
  560. (walk/postwalk
  561. (fn [x]
  562. (cond
  563. (and (map? x) (contains? x :block/uuid))
  564. (-> x
  565. (s/rename-keys {:block/uuid :block/id
  566. :block/original-name :block/page-name})
  567. (dissoc-properties [:id])
  568. (select-keys keyseq))
  569. :else
  570. x))
  571. vec-tree))
  572. (defn- blocks [conn]
  573. {:version 1
  574. :blocks
  575. (->> (d/q '[:find (pull ?b [*])
  576. :in $
  577. :where
  578. [?b :block/file]
  579. [?b :block/original-name]
  580. [?b :block/name]] conn)
  581. (map (fn [[{:block/keys [name] :as page}]]
  582. (assoc page
  583. :block/children
  584. (outliner-tree/blocks->vec-tree
  585. (db/get-page-blocks-no-cache
  586. (state/get-current-repo)
  587. name
  588. {:transform? false}) name))))
  589. (nested-select-keys
  590. [:block/id
  591. :block/page-name
  592. :block/properties
  593. :block/heading-level
  594. :block/format
  595. :block/children
  596. :block/title
  597. :block/body
  598. :block/content]))})
  599. (defn- file-name [repo extension]
  600. (-> (string/replace repo config/local-db-prefix "")
  601. (string/replace #"^/+" "")
  602. (str "_" (quot (util/time-ms) 1000))
  603. (str "." (string/lower-case (name extension)))))
  604. (defn export-repo-as-edn-v2!
  605. [repo]
  606. (when-let [conn (db/get-conn repo)]
  607. (let [edn-str (with-out-str
  608. (pprint/pprint
  609. (blocks conn)))
  610. data-str (str "data:text/edn;charset=utf-8," (js/encodeURIComponent edn-str))]
  611. (when-let [anchor (gdom/getElement "download-as-edn-v2")]
  612. (.setAttribute anchor "href" data-str)
  613. (.setAttribute anchor "download" (file-name repo :edn))
  614. (.click anchor)))))
  615. (defn- nested-update-id
  616. [vec-tree]
  617. (walk/postwalk
  618. (fn [x]
  619. (if (and (map? x) (contains? x :block/id))
  620. (update x :block/id str)
  621. x))
  622. vec-tree))
  623. (defn export-repo-as-json-v2!
  624. [repo]
  625. (when-let [conn (db/get-conn repo)]
  626. (let [json-str
  627. (-> (blocks conn)
  628. nested-update-id
  629. clj->js
  630. js/JSON.stringify)
  631. data-str (str "data:text/json;charset=utf-8,"
  632. (js/encodeURIComponent json-str))]
  633. (when-let [anchor (gdom/getElement "download-as-json-v2")]
  634. (.setAttribute anchor "href" data-str)
  635. (.setAttribute anchor "download" (file-name repo :json))
  636. (.click anchor)))))
  637. ;;;;;;;;;;;;;;;;;;;;;;;;;
  638. ;; Export to roam json ;;
  639. ;;;;;;;;;;;;;;;;;;;;;;;;;
  640. ;; https://roamresearch.com/#/app/help/page/Nxz8u0vXU
  641. ;; export to roam json according to above spec
  642. (defn- roam-json [conn]
  643. (->> (d/q '[:find (pull ?b [*])
  644. :in $
  645. :where
  646. [?b :block/file]
  647. [?b :block/original-name]
  648. [?b :block/name]] conn)
  649. (map (fn [[{:block/keys [name] :as page}]]
  650. (assoc page
  651. :block/children
  652. (outliner-tree/blocks->vec-tree
  653. (db/get-page-blocks-no-cache
  654. (state/get-current-repo)
  655. name
  656. {:transform? false}) name))))
  657. (roam-export/traverse
  658. [:page/title
  659. :block/string
  660. :block/uid
  661. :block/children])))
  662. (defn export-repo-as-roam-json!
  663. [repo]
  664. (when-let [conn (db/get-conn repo)]
  665. (let [json-str
  666. (-> (roam-json conn)
  667. clj->js
  668. js/JSON.stringify)
  669. data-str (str "data:text/json;charset=utf-8,"
  670. (js/encodeURIComponent json-str))]
  671. (when-let [anchor (gdom/getElement "download-as-roam-json")]
  672. (.setAttribute anchor "href" data-str)
  673. (.setAttribute anchor "download" (file-name (str repo "_roam") :json))
  674. (.click anchor)))))