export.cljs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. (ns frontend.handler.export
  2. (:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
  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.mldoc :as mldoc]
  14. [frontend.format.protocol :as fp]
  15. [frontend.mobile.util :as mobile-util]
  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. [frontend.util.property :as property]
  22. [goog.dom :as gdom]
  23. [lambdaisland.glogi :as log]
  24. [logseq.graph-parser.mldoc :as gp-mldoc]
  25. [logseq.graph-parser.util :as gp-util]
  26. [promesa.core :as p]
  27. [frontend.handler.notification :as notification])
  28. (:import
  29. [goog.string StringBuffer]))
  30. (defn- get-page-content
  31. [repo page]
  32. (outliner-file/tree->file-content
  33. (outliner-tree/blocks->vec-tree
  34. (db/get-page-blocks-no-cache repo page) page) {:init-level 1}))
  35. (defn- get-file-content
  36. [repo file-path]
  37. (if-let [page-name
  38. (ffirst (d/q '[:find ?pn
  39. :in $ ?path
  40. :where
  41. [?p :block/file ?f]
  42. [?p :block/name ?pn]
  43. [?f :file/path ?path]]
  44. (db/get-db repo) file-path))]
  45. (get-page-content repo page-name)
  46. (ffirst
  47. (d/q '[:find ?content
  48. :in $ ?path
  49. :where
  50. [?f :file/path ?path]
  51. [?f :file/content ?content]]
  52. (db/get-db repo) file-path))))
  53. (defn- get-blocks-contents
  54. [repo root-block-uuid]
  55. (->
  56. (db/get-block-and-children repo root-block-uuid)
  57. (outliner-tree/blocks->vec-tree (str root-block-uuid))
  58. (outliner-file/tree->file-content {:init-level 1})))
  59. (defn- get-block-content
  60. [block]
  61. (->
  62. [block]
  63. (outliner-tree/blocks->vec-tree (str (:block/uuid block)))
  64. (outliner-file/tree->file-content {:init-level 1})))
  65. (defn download-file!
  66. [file-path]
  67. (when-let [repo (state/get-current-repo)]
  68. (when-let [content (get-file-content repo file-path)]
  69. (let [data (js/Blob. ["\ufeff" (array content)] ; prepend BOM
  70. (clj->js {:type "text/plain;charset=utf-8,"}))
  71. anchor (gdom/getElement "download")
  72. url (js/window.URL.createObjectURL data)]
  73. (.setAttribute anchor "href" url)
  74. (.setAttribute anchor "download" file-path)
  75. (.click anchor)))))
  76. (defn export-repo-as-html!
  77. [repo]
  78. (when-let [db (db/get-db repo)]
  79. (let [[db asset-filenames] (if (state/all-pages-public?)
  80. (db/clean-export! db)
  81. (db/filter-only-public-pages-and-blocks db))
  82. db-str (db/db->string db)
  83. state (select-keys @state/state
  84. [:ui/theme
  85. :ui/sidebar-collapsed-blocks
  86. :ui/show-recent?
  87. :config])
  88. state (update state :config (fn [config]
  89. {"local" (get config repo)}))
  90. raw-html-str (html/publishing-html db-str (pr-str state))
  91. html-str (str "data:text/html;charset=UTF-8,"
  92. (js/encodeURIComponent raw-html-str))]
  93. (if (util/electron?)
  94. (js/window.apis.exportPublishAssets
  95. raw-html-str
  96. (config/get-custom-css-path)
  97. (config/get-export-css-path)
  98. (config/get-repo-dir repo)
  99. (clj->js asset-filenames)
  100. (util/mocked-open-dir-path))
  101. (when-let [anchor (gdom/getElement "download-as-html")]
  102. (.setAttribute anchor "href" html-str)
  103. (.setAttribute anchor "download" "index.html")
  104. (.click anchor))))))
  105. (defn- get-file-contents
  106. ([repo]
  107. (get-file-contents repo {:init-level 1}))
  108. ([repo file-opts]
  109. (let [db (db/get-db repo)]
  110. (->> (d/q '[:find ?n ?fp
  111. :where
  112. [?e :block/file ?f]
  113. [?f :file/path ?fp]
  114. [?e :block/name ?n]] db)
  115. (mapv (fn [[page-name file-path]]
  116. [file-path
  117. (outliner-file/tree->file-content
  118. (outliner-tree/blocks->vec-tree
  119. (db/get-page-blocks-no-cache page-name) page-name)
  120. file-opts)]))))))
  121. (defn export-repo-as-zip!
  122. [repo]
  123. (let [files (get-file-contents repo)
  124. [owner repo-name] (util/get-git-owner-and-repo repo)
  125. repo-name (str owner "-" repo-name)]
  126. (when (seq files)
  127. (p/let [zipfile (zip/make-zip repo-name files repo)]
  128. (when-let [anchor (gdom/getElement "download")]
  129. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  130. (.setAttribute anchor "download" (.-name zipfile))
  131. (.click anchor))))))
  132. (defn get-md-file-contents
  133. [repo]
  134. #_:clj-kondo/ignore
  135. (filter (fn [[path _]]
  136. (let [path (string/lower-case path)]
  137. (re-find #"\.(?:md|markdown)$" path)))
  138. (get-file-contents repo {:init-level 1
  139. :heading-to-list? true})))
  140. (defn- get-embed-pages-from-ast [ast]
  141. (let [result (transient #{})]
  142. (doseq [item ast]
  143. (walk/prewalk (fn [i]
  144. (cond
  145. (and (vector? i)
  146. (= "Macro" (first i))
  147. (= "embed" (some-> (:name (second i))
  148. (string/lower-case)))
  149. (some-> (:arguments (second i))
  150. (first)
  151. (string/starts-with? "[["))
  152. (some-> (:arguments (second i))
  153. (first)
  154. (string/ends-with? "]]")))
  155. (let [arguments (:arguments (second i))
  156. page-ref (first arguments)
  157. page-name (-> page-ref
  158. (subs 2)
  159. (#(subs % 0 (- (count %) 2)))
  160. (string/lower-case))]
  161. (conj! result page-name)
  162. i)
  163. :else
  164. i))
  165. item))
  166. (persistent! result)))
  167. (defn- get-embed-blocks-from-ast [ast]
  168. (let [result (transient #{})]
  169. (doseq [item ast]
  170. (walk/prewalk (fn [i]
  171. (cond
  172. (and (vector? i)
  173. (= "Macro" (first i))
  174. (= "embed" (some-> (:name (second i))
  175. (string/lower-case)))
  176. (some-> (:arguments (second i))
  177. (first)
  178. (string/starts-with? "(("))
  179. (some-> (:arguments (second i))
  180. (first)
  181. (string/ends-with? "))")))
  182. (let [arguments (:arguments (second i))
  183. block-ref (first arguments)
  184. block-uuid (-> block-ref
  185. (subs 2)
  186. (#(subs % 0 (- (count %) 2))))]
  187. (conj! result block-uuid)
  188. i)
  189. :else
  190. i)) item))
  191. (persistent! result)))
  192. (defn- get-block-refs-from-ast [ast]
  193. (let [result (transient #{})]
  194. (doseq [item ast]
  195. (walk/prewalk (fn [i]
  196. (cond
  197. (and (vector? i)
  198. (= "Block_ref" (first i))
  199. (some? (second i)))
  200. (let [block-uuid (second i)]
  201. (conj! result block-uuid)
  202. i)
  203. :else
  204. i)) item))
  205. (persistent! result)))
  206. (declare get-page-page&block-refs)
  207. (defn get-block-page&block-refs [repo block-uuid embed-pages embed-blocks block-refs]
  208. (let [block (db/entity [:block/uuid (uuid block-uuid)])
  209. block-content (get-blocks-contents repo (:block/uuid block))
  210. format (:block/format block)
  211. ast (mldoc/->edn block-content (gp-mldoc/default-config format))
  212. embed-pages-new (get-embed-pages-from-ast ast)
  213. embed-blocks-new (get-embed-blocks-from-ast ast)
  214. block-refs-new (get-block-refs-from-ast ast)
  215. embed-pages-diff (s/difference embed-pages-new embed-pages)
  216. embed-blocks-diff (s/difference embed-blocks-new embed-blocks)
  217. block-refs-diff (s/difference block-refs-new block-refs)
  218. embed-pages* (s/union embed-pages-new embed-pages)
  219. embed-blocks* (s/union embed-blocks-new embed-blocks)
  220. block-refs* (s/union block-refs-new block-refs)
  221. [embed-pages-1 embed-blocks-1 block-refs-1]
  222. (->>
  223. (mapv (fn [page-name]
  224. (let [{:keys [embed-pages embed-blocks block-refs]}
  225. (get-page-page&block-refs repo page-name embed-pages* embed-blocks* block-refs*)]
  226. [embed-pages embed-blocks block-refs])) embed-pages-diff)
  227. (apply mapv vector) ; [[1 2 3] [4 5 6] [7 8 9]] -> [[1 4 7] [2 5 8] [3 6 9]]
  228. (mapv #(apply s/union %)))
  229. [embed-pages-2 embed-blocks-2 block-refs-2]
  230. (->>
  231. (mapv (fn [block-uuid]
  232. (let [{:keys [embed-pages embed-blocks block-refs]}
  233. (get-block-page&block-refs repo block-uuid embed-pages* embed-blocks* block-refs*)]
  234. [embed-pages embed-blocks block-refs])) (s/union embed-blocks-diff block-refs-diff))
  235. (apply mapv vector)
  236. (mapv #(apply s/union %)))]
  237. {:embed-pages (s/union embed-pages-1 embed-pages-2 embed-pages*)
  238. :embed-blocks (s/union embed-blocks-1 embed-blocks-2 embed-blocks*)
  239. :block-refs (s/union block-refs-1 block-refs-2 block-refs*)}))
  240. (defn get-blocks-page&block-refs [repo block-uuids embed-pages embed-blocks block-refs]
  241. (let [[embed-pages embed-blocks block-refs]
  242. (reduce (fn [[embed-pages embed-blocks block-refs] block-uuid]
  243. (let [result (get-block-page&block-refs repo block-uuid embed-pages embed-blocks block-refs)]
  244. [(:embed-pages result) (:embed-blocks result) (:block-refs result)]))
  245. [embed-pages embed-blocks block-refs] block-uuids)]
  246. {:embed-pages embed-pages
  247. :embed-blocks embed-blocks
  248. :block-refs block-refs}))
  249. (defn get-page-page&block-refs [repo page-name embed-pages embed-blocks block-refs]
  250. (let [page-name* (util/page-name-sanity-lc page-name)
  251. page-content (get-page-content repo page-name*)
  252. format (:block/format (db/entity [:block/name page-name*]))
  253. ast (mldoc/->edn page-content (gp-mldoc/default-config format))
  254. embed-pages-new (get-embed-pages-from-ast ast)
  255. embed-blocks-new (get-embed-blocks-from-ast ast)
  256. block-refs-new (get-block-refs-from-ast ast)
  257. embed-pages-diff (s/difference embed-pages-new embed-pages)
  258. embed-blocks-diff (s/difference embed-blocks-new embed-blocks)
  259. block-refs-diff (s/difference block-refs-new block-refs)
  260. embed-pages* (s/union embed-pages-new embed-pages)
  261. embed-blocks* (s/union embed-blocks-new embed-blocks)
  262. block-refs* (s/union block-refs-new block-refs)
  263. [embed-pages-1 embed-blocks-1 block-refs-1]
  264. (->>
  265. (mapv (fn [page-name]
  266. (let [{:keys [embed-pages embed-blocks block-refs]}
  267. (get-page-page&block-refs repo page-name embed-pages* embed-blocks* block-refs*)]
  268. [embed-pages embed-blocks block-refs])) embed-pages-diff)
  269. (apply mapv vector)
  270. (mapv #(apply s/union %)))
  271. [embed-pages-2 embed-blocks-2 block-refs-2]
  272. (->>
  273. (mapv (fn [block-uuid]
  274. (let [{:keys [embed-pages embed-blocks block-refs]}
  275. (get-block-page&block-refs repo block-uuid embed-pages* embed-blocks* block-refs*)]
  276. [embed-pages embed-blocks block-refs])) (s/union embed-blocks-diff block-refs-diff))
  277. (apply mapv vector)
  278. (mapv #(apply s/union %)))]
  279. {:embed-pages (s/union embed-pages-1 embed-pages-2 embed-pages*)
  280. :embed-blocks (s/union embed-blocks-1 embed-blocks-2 embed-blocks*)
  281. :block-refs (s/union block-refs-1 block-refs-2 block-refs*)}))
  282. (defn- get-export-references [repo {:keys [embed-pages embed-blocks block-refs]}]
  283. (let [embed-blocks-and-contents
  284. (mapv (fn [id]
  285. (let [id-s (str id)
  286. id (uuid id-s)]
  287. [id-s
  288. [(get-blocks-contents repo id)
  289. (get-block-content (db/pull [:block/uuid id]))]]))
  290. (s/union embed-blocks block-refs))
  291. embed-pages-and-contents
  292. (mapv (fn [page-name] [page-name (get-page-content repo page-name)]) embed-pages)]
  293. {:embed_blocks embed-blocks-and-contents
  294. :embed_pages embed-pages-and-contents}))
  295. (defn- export-files-as-markdown [repo files heading-to-list?]
  296. (->> files
  297. (mapv (fn [{:keys [path content names format]}]
  298. (when (first names)
  299. [path (fp/exportMarkdown f/mldoc-record content
  300. (f/get-default-config format {:export-heading-to-list? heading-to-list?})
  301. (js/JSON.stringify
  302. (clj->js (get-export-references
  303. repo
  304. (get-page-page&block-refs repo (first names) #{} #{} #{})))))])))))
  305. (defn- export-files-as-opml [repo files]
  306. (->> files
  307. (mapv (fn [{:keys [path content names format]}]
  308. (when (first names)
  309. (let [path
  310. (string/replace
  311. (string/lower-case path) #"(.+)\.(md|markdown|org)" "$1.opml")]
  312. [path (fp/exportOPML f/mldoc-record content
  313. (f/get-default-config format)
  314. (first names)
  315. (js/JSON.stringify
  316. (clj->js (get-export-references
  317. repo
  318. (get-page-page&block-refs repo (first names) #{} #{} #{})))))]))))))
  319. (defn export-blocks-as-aux
  320. [repo root-block-uuids auxf]
  321. {:pre [(> (count root-block-uuids) 0)]}
  322. (let [f #(get-export-references repo (get-blocks-page&block-refs repo % #{} #{} #{}))
  323. root-blocks (mapv #(db/entity [:block/uuid %]) root-block-uuids)
  324. blocks (mapcat #(db/get-block-and-children repo %) root-block-uuids)
  325. refs (f (mapv #(str (:block/uuid %)) blocks))
  326. contents (mapv #(get-blocks-contents repo %) root-block-uuids)
  327. content (string/join "\n" (mapv string/trim-newline contents))
  328. format (or (:block/format (first root-blocks)) (state/get-preferred-format))]
  329. (auxf content format refs)))
  330. (defn export-blocks-as-opml
  331. [repo root-block-uuids]
  332. (export-blocks-as-aux repo root-block-uuids
  333. #(fp/exportOPML f/mldoc-record %1
  334. (f/get-default-config %2)
  335. "untitled"
  336. (js/JSON.stringify (clj->js %3)))))
  337. (defn export-blocks-as-markdown
  338. [repo root-block-uuids indent-style remove-options]
  339. (export-blocks-as-aux repo root-block-uuids
  340. #(fp/exportMarkdown f/mldoc-record %1
  341. (f/get-default-config
  342. %2
  343. {:export-md-indent-style indent-style
  344. :export-md-remove-options remove-options})
  345. (js/JSON.stringify (clj->js %3)))))
  346. (defn export-blocks-as-html
  347. [repo root-block-uuids]
  348. (export-blocks-as-aux repo root-block-uuids
  349. #(fp/toHtml f/mldoc-record %1
  350. (f/get-default-config %2)
  351. (js/JSON.stringify (clj->js %3)))))
  352. (defn- get-file-contents-with-suffix
  353. [repo]
  354. (let [db (db/get-db repo)
  355. md-files (get-md-file-contents repo)]
  356. (->>
  357. md-files
  358. (map (fn [[path content]] {:path path :content content
  359. :names (d/q '[:find [?n ?n2]
  360. :in $ ?p
  361. :where [?e :file/path ?p]
  362. [?e2 :block/file ?e]
  363. [?e2 :block/name ?n]
  364. [?e2 :block/original-name ?n2]] db path)
  365. :format (gp-util/get-format path)})))))
  366. (defn- export-file-on-mobile [data path]
  367. (p/catch
  368. (.writeFile Filesystem (clj->js {:path path
  369. :data data
  370. :encoding (.-UTF8 Encoding)
  371. :recursive true}))
  372. (notification/show! "Export succeeded! You can find you exported file in the root directory of your graph." :success)
  373. (fn [error]
  374. (notification/show! "Export failed!" :error)
  375. (log/error :export-file-failed error))))
  376. (defn export-repo-as-markdown!
  377. [repo]
  378. (when-let [files (get-file-contents-with-suffix repo)]
  379. (let [heading-to-list? (state/export-heading-to-list?)
  380. files
  381. (export-files-as-markdown repo files heading-to-list?)
  382. zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
  383. (p/let [zipfile (zip/make-zip zip-file-name files repo)]
  384. (when-let [anchor (gdom/getElement "export-as-markdown")]
  385. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  386. (.setAttribute anchor "download" (.-name zipfile))
  387. (.click anchor))))))
  388. (defn export-repo-as-opml!
  389. #_:clj-kondo/ignore
  390. [repo]
  391. (when-let [repo (state/get-current-repo)]
  392. (when-let [files (get-file-contents-with-suffix repo)]
  393. (let [files (export-files-as-opml repo files)
  394. zip-file-name (str repo "_opml_" (quot (util/time-ms) 1000))]
  395. (p/let [zipfile (zip/make-zip zip-file-name files repo)]
  396. (when-let [anchor (gdom/getElement "export-as-opml")]
  397. (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
  398. (.setAttribute anchor "download" (.-name zipfile))
  399. (.click anchor)))))))
  400. (defn- dissoc-properties [m ks]
  401. (if (:block/properties m)
  402. (update m :block/properties
  403. (fn [v]
  404. (apply dissoc v ks)))
  405. m))
  406. (defn- nested-select-keys
  407. [keyseq vec-tree]
  408. (walk/postwalk
  409. (fn [x]
  410. (cond
  411. (and (map? x) (contains? x :block/uuid))
  412. (-> x
  413. (s/rename-keys {:block/uuid :block/id
  414. :block/original-name :block/page-name})
  415. (dissoc-properties [:id])
  416. (select-keys keyseq))
  417. :else
  418. x))
  419. vec-tree))
  420. (defn- blocks [db]
  421. {:version 1
  422. :blocks
  423. (->> (d/q '[:find (pull ?b [*])
  424. :in $
  425. :where
  426. [?b :block/file]
  427. [?b :block/original-name]
  428. [?b :block/name]] db)
  429. (map (fn [[{:block/keys [name] :as page}]]
  430. (let [blocks (db/get-page-blocks-no-cache
  431. (state/get-current-repo)
  432. name
  433. {:transform? false})
  434. blocks' (map (fn [b]
  435. (if (seq (:block/properties b))
  436. (update b :block/content
  437. (fn [content] (property/remove-properties (:block/format b) content)))
  438. b)) blocks)
  439. children (outliner-tree/blocks->vec-tree blocks' name)]
  440. (assoc page :block/children children))))
  441. (nested-select-keys
  442. [:block/id
  443. :block/page-name
  444. :block/properties
  445. :block/heading-level
  446. :block/format
  447. :block/children
  448. :block/content]))})
  449. (defn- file-name [repo extension]
  450. (-> (string/replace repo config/local-db-prefix "")
  451. (string/replace #"^/+" "")
  452. (str "_" (quot (util/time-ms) 1000))
  453. (str "." (string/lower-case (name extension)))))
  454. (defn- export-repo-as-edn-str [repo]
  455. (when-let [db (db/get-db repo)]
  456. (let [sb (StringBuffer.)]
  457. (pprint/pprint (blocks db) (StringBufferWriter. sb))
  458. (str sb))))
  459. (defn export-repo-as-edn-v2!
  460. [repo]
  461. (when-let [edn-str (export-repo-as-edn-str repo)]
  462. (let [data-str (some->> edn-str
  463. js/encodeURIComponent
  464. (str "data:text/edn;charset=utf-8,"))
  465. filename (file-name repo :edn)]
  466. (if (mobile-util/native-platform?)
  467. (export-file-on-mobile edn-str filename)
  468. (when-let [anchor (gdom/getElement "download-as-edn-v2")]
  469. (.setAttribute anchor "href" data-str)
  470. (.setAttribute anchor "download" filename)
  471. (.click anchor))))))
  472. (defn- nested-update-id
  473. [vec-tree]
  474. (walk/postwalk
  475. (fn [x]
  476. (if (and (map? x) (contains? x :block/id))
  477. (update x :block/id str)
  478. x))
  479. vec-tree))
  480. (defn export-repo-as-json-v2!
  481. [repo]
  482. (when-let [db (db/get-db repo)]
  483. (let [json-str
  484. (-> (blocks db)
  485. nested-update-id
  486. clj->js
  487. js/JSON.stringify)
  488. filename (file-name repo :json)
  489. data-str (str "data:text/json;charset=utf-8,"
  490. (js/encodeURIComponent json-str))]
  491. (if (mobile-util/native-platform?)
  492. (export-file-on-mobile json-str filename)
  493. (when-let [anchor (gdom/getElement "download-as-json-v2")]
  494. (.setAttribute anchor "href" data-str)
  495. (.setAttribute anchor "download" filename)
  496. (.click anchor))))))
  497. ;;;;;;;;;;;;;;;;;;;;;;;;;
  498. ;; Export to roam json ;;
  499. ;;;;;;;;;;;;;;;;;;;;;;;;;
  500. ;; https://roamresearch.com/#/app/help/page/Nxz8u0vXU
  501. ;; export to roam json according to above spec
  502. (defn- roam-json [db]
  503. (->> (d/q '[:find (pull ?b [*])
  504. :in $
  505. :where
  506. [?b :block/file]
  507. [?b :block/original-name]
  508. [?b :block/name]] db)
  509. (map (fn [[{:block/keys [name] :as page}]]
  510. (assoc page
  511. :block/children
  512. (outliner-tree/blocks->vec-tree
  513. (db/get-page-blocks-no-cache
  514. (state/get-current-repo)
  515. name
  516. {:transform? false}) name))))
  517. (roam-export/traverse
  518. [:page/title
  519. :block/string
  520. :block/uid
  521. :block/children])))
  522. (defn export-repo-as-roam-json!
  523. [repo]
  524. (when-let [db (db/get-db repo)]
  525. (let [json-str
  526. (-> (roam-json db)
  527. clj->js
  528. js/JSON.stringify)
  529. data-str (str "data:text/json;charset=utf-8,"
  530. (js/encodeURIComponent json-str))]
  531. (when-let [anchor (gdom/getElement "download-as-roam-json")]
  532. (.setAttribute anchor "href" data-str)
  533. (.setAttribute anchor "download" (file-name (str repo "_roam") :json))
  534. (.click anchor)))))