import.cljs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. (ns frontend.handler.import
  2. "Fns related to import from external services"
  3. (:require [clojure.edn :as edn]
  4. [clojure.walk :as walk]
  5. [frontend.external :as external]
  6. [frontend.handler.file :as file-handler]
  7. [frontend.handler.repo :as repo-handler]
  8. [frontend.handler.file-based.repo :as file-repo-handler]
  9. [frontend.state :as state]
  10. [frontend.date :as date]
  11. [frontend.config :as config]
  12. [clojure.string :as string]
  13. [frontend.db :as db]
  14. [frontend.format.mldoc :as mldoc]
  15. [frontend.format.block :as block]
  16. [logseq.graph-parser.mldoc :as gp-mldoc]
  17. [logseq.common.util :as common-util]
  18. [logseq.graph-parser.whiteboard :as gp-whiteboard]
  19. [logseq.common.util.date-time :as date-time-util]
  20. [frontend.handler.page :as page-handler]
  21. [frontend.handler.editor :as editor]
  22. [frontend.handler.notification :as notification]
  23. [frontend.util :as util]
  24. [clojure.core.async :as async]
  25. [cljs.core.async.interop :refer [p->c]]
  26. [medley.core :as medley]
  27. [frontend.persist-db :as persist-db]
  28. [promesa.core :as p]
  29. [frontend.db.async :as db-async]))
  30. (defn index-files!
  31. "Create file structure, then parse into DB (client only)"
  32. [repo files finish-handler]
  33. (let [titles (->> files
  34. (map :title)
  35. (remove nil?))
  36. files (map (fn [file]
  37. (let [title (:title file)
  38. journal? (date/valid-journal-title? title)]
  39. (when-let [text (:text file)]
  40. (let [title (or
  41. (when journal?
  42. (date/journal-title->default title))
  43. (string/replace title "/" "-"))
  44. title (-> (common-util/page-name-sanity title)
  45. (string/replace "\n" " "))
  46. path (str (if journal?
  47. (config/get-journals-directory)
  48. (config/get-pages-directory))
  49. "/"
  50. title
  51. ".md")]
  52. {:file/path path
  53. :file/content text}))))
  54. files)
  55. files (remove nil? files)]
  56. (file-repo-handler/parse-files-and-load-to-db! repo files nil)
  57. (let [files (->> (map (fn [{:file/keys [path content]}] (when path [path content])) files)
  58. (remove nil?))]
  59. (file-handler/alter-files repo files {:add-history? false
  60. :update-db? false
  61. :update-status? false
  62. :finish-handler finish-handler}))
  63. (let [journal-pages-tx (let [titles (filter date/normalize-journal-title titles)]
  64. (map
  65. (fn [title]
  66. (let [day (date/journal-title->int title)
  67. journal-title (date-time-util/int->journal-title day (state/get-date-formatter))]
  68. (when journal-title
  69. (let [page-name (util/page-name-sanity-lc journal-title)]
  70. {:block/name page-name
  71. :block/type "journal"
  72. :block/journal-day day}))))
  73. titles))]
  74. (when (seq journal-pages-tx)
  75. (db/transact! repo journal-pages-tx)))))
  76. ;; TODO: replace files with page blocks transaction
  77. (defn import-from-roam-json!
  78. [data finished-ok-handler]
  79. (when-let [repo (state/get-current-repo)]
  80. (let [files (external/to-markdown-files :roam data {})]
  81. (index-files! repo files
  82. (fn []
  83. (finished-ok-handler))))))
  84. ;;; import OPML files
  85. (defn import-from-opml!
  86. [data finished-ok-handler]
  87. #_:clj-kondo/ignore
  88. (when-let [repo (state/get-current-repo)]
  89. (let [config (gp-mldoc/default-config :markdown)
  90. [headers parsed-blocks] (mldoc/opml->edn config data)
  91. ;; add empty pos metadata
  92. parsed-blocks (map (fn [b] [b {}]) parsed-blocks)
  93. page-name (:title headers)
  94. parsed-blocks (->>
  95. (block/extract-blocks parsed-blocks "" :markdown {:page-name page-name})
  96. (mapv editor/wrap-parse-block))]
  97. (p/do!
  98. (when (not (db/page-exists? page-name))
  99. (page-handler/<create! page-name {:redirect? false}))
  100. (let [page-block (db/get-page page-name)
  101. children (:block/_parent page-block)
  102. blocks (db/sort-by-order children)
  103. last-block (last blocks)
  104. snd-last-block (last (butlast blocks))
  105. [target-block sibling?] (if (and last-block (seq (:block/title last-block)))
  106. [last-block true]
  107. (if snd-last-block
  108. [snd-last-block true]
  109. [page-block false]))]
  110. (editor/paste-blocks
  111. parsed-blocks
  112. {:target-block target-block
  113. :sibling? sibling?})
  114. (finished-ok-handler [page-name]))))))
  115. (defn create-page-with-exported-tree!
  116. "Create page from the per page object generated in `export-repo-as-edn-v2!`
  117. Return page-name (title)
  118. Extension to `insert-block-tree-after-target`
  119. :id - page's uuid
  120. :title - page's title (original name)
  121. :children - tree
  122. :properties - map
  123. "
  124. [{:keys [type uuid title children properties] :as tree}]
  125. (let [title (string/trim title)
  126. has-children? (seq children)
  127. page-format (or (some-> tree (:children) (first) (:format)) :markdown)
  128. whiteboard? (= type "whiteboard")]
  129. (p/do!
  130. (try (page-handler/<create! title {:redirect? false
  131. :format page-format
  132. :uuid uuid
  133. :create-first-block? false
  134. :properties properties
  135. :whiteboard? whiteboard?})
  136. (catch :default e
  137. (js/console.error e)
  138. (prn {:tree tree})
  139. (notification/show! (str "Error happens when creating page " title ":\n"
  140. e
  141. "\nSkipped and continue the remaining import.") :error)))
  142. (when has-children?
  143. (let [page-name (util/page-name-sanity-lc title)
  144. page-block (db/get-page page-name)]
  145. ;; Missing support for per block format (or deprecated?)
  146. (try (if whiteboard?
  147. ;; only works for file graph :block/properties
  148. (let [blocks (->> children
  149. (map (partial medley/map-keys (fn [k] (keyword "block" k))))
  150. (map gp-whiteboard/migrate-shape-block)
  151. (map #(merge % (gp-whiteboard/with-whiteboard-block-props % [:block/uuid uuid]))))]
  152. (db/transact! blocks))
  153. (editor/insert-block-tree children page-format
  154. {:target-block page-block
  155. :sibling? false
  156. :keep-uuid? true}))
  157. (catch :default e
  158. (js/console.error e)
  159. (prn {:tree tree})
  160. (notification/show! (str "Error happens when creating block content of page " title "\n"
  161. e
  162. "\nSkipped and continue the remaining import.") :error)))))))
  163. title)
  164. (defn- pre-transact-uuids!
  165. "Collect all uuids from page trees and write them to the db before hand."
  166. [pages]
  167. (let [uuids (mapv (fn [block]
  168. {:block/uuid (:uuid block)})
  169. (mapcat #(tree-seq map? :children %)
  170. pages))]
  171. (db/transact! uuids)))
  172. (defn- import-from-tree!
  173. "Not rely on file system - backend compatible.
  174. tree-translator-fn: translate exported tree structure to the desired tree for import"
  175. [data tree-translator-fn]
  176. (let [imported-chan (async/promise-chan)]
  177. (try
  178. (let [blocks (->> (:blocks data)
  179. (mapv tree-translator-fn )
  180. (sort-by :title)
  181. (medley/indexed))
  182. job-chan (async/to-chan! blocks)]
  183. (state/set-state! [:graph/importing-state :total] (count blocks))
  184. (pre-transact-uuids! blocks)
  185. (async/go-loop []
  186. (if-let [[i block] (async/<! job-chan)]
  187. (do
  188. (state/set-state! [:graph/importing-state :current-idx] (inc i))
  189. (state/set-state! [:graph/importing-state :current-page] (:title block))
  190. (async/<! (async/timeout 10))
  191. (create-page-with-exported-tree! block)
  192. (recur))
  193. (let [result (async/<! (p->c (db-async/<get-all-referenced-blocks-uuid (state/get-current-repo))))]
  194. (editor/set-blocks-id! result)
  195. (async/offer! imported-chan true)))))
  196. (catch :default e
  197. (notification/show! (str "Error happens when importing:\n" e) :error)
  198. (async/offer! imported-chan true)))))
  199. (defn tree-vec-translate-edn
  200. "Actions to do for loading edn tree structure.
  201. 1) Removes namespace `:block/` from all levels of the `tree-vec`
  202. 2) Rename all :block/page-name to :title
  203. 3) Rename all :block/id to :uuid"
  204. ([tree-vec]
  205. (let [kw-trans-fn #(-> %
  206. str
  207. (string/replace ":block/page-name" ":block/title")
  208. (string/replace ":block/id" ":block/uuid")
  209. (string/replace ":block/" "")
  210. keyword)
  211. map-trans-fn (fn [acc k v]
  212. (assoc acc (kw-trans-fn k) v))
  213. tree-trans-fn (fn [form]
  214. (if (and (map? form)
  215. (:block/id form))
  216. (reduce-kv map-trans-fn {} form)
  217. form))]
  218. (walk/postwalk tree-trans-fn tree-vec))))
  219. (defn import-from-sqlite-db!
  220. [buffer bare-graph-name finished-ok-handler]
  221. (let [graph (str config/db-version-prefix bare-graph-name)]
  222. (-> (p/let [_ (persist-db/<import-db graph buffer)]
  223. (state/add-repo! {:url graph})
  224. (repo-handler/restore-and-setup-repo! graph))
  225. (p/then
  226. (fn [_result]
  227. (state/set-current-repo! graph)
  228. (persist-db/<export-db graph {})
  229. (finished-ok-handler)))
  230. (p/catch
  231. (fn [e]
  232. (js/console.error e)
  233. (notification/show!
  234. (str (.-message e))
  235. :error))))))
  236. (defn import-from-edn!
  237. [raw finished-ok-handler]
  238. (try
  239. (let [data (edn/read-string raw)]
  240. (async/go
  241. (async/<! (import-from-tree! data tree-vec-translate-edn))
  242. (finished-ok-handler nil)))
  243. (catch :default e
  244. (js/console.error e)
  245. (notification/show!
  246. (str (.-message e))
  247. :error)))) ;; it was designed to accept a list of imported page names but now deprecated
  248. (defn tree-vec-translate-json
  249. "Actions to do for loading json tree structure.
  250. 1) Rename all :id to :uuid
  251. 2) Rename all :page-name to :title
  252. 3) Rename all :format \"markdown\" to :format `:markdown`"
  253. ([tree-vec]
  254. (let [kw-trans-fn #(-> %
  255. str
  256. (string/replace ":page-name" ":title")
  257. (string/replace ":id" ":uuid")
  258. (string/replace #"^:" "")
  259. keyword)
  260. map-trans-fn (fn [acc k v]
  261. (cond (= :format k)
  262. (assoc acc (kw-trans-fn k) (keyword v))
  263. (= :id k)
  264. (assoc acc (kw-trans-fn k) (uuid v))
  265. :else
  266. (assoc acc (kw-trans-fn k) v)))
  267. tree-trans-fn (fn [form]
  268. (if (and (map? form)
  269. (:id form))
  270. (reduce-kv map-trans-fn {} form)
  271. form))]
  272. (walk/postwalk tree-trans-fn tree-vec))))
  273. (defn import-from-json!
  274. [raw finished-ok-handler]
  275. (let [json (js/JSON.parse raw)
  276. clj-data (js->clj json :keywordize-keys true)]
  277. (async/go
  278. (async/<! (import-from-tree! clj-data tree-vec-translate-json))
  279. (finished-ok-handler nil)))) ;; it was designed to accept a list of imported page names but now deprecated