Browse Source

feat: export as roam json

Weihua Lu 4 years ago
parent
commit
dc8122f5a6

+ 4 - 0
src/main/frontend/components/export.cljs

@@ -33,10 +33,14 @@
         [:li.mb-4
          [:a.font-medium {:on-click #(export/export-repo-as-json-v2! current-repo)}
           (t :export-json)]]
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/export-repo-as-roam-json! current-repo)}
+          (t :export-roam-json)]]
         ]
        [:a#download-as-edn.hidden]
        [:a#download-as-edn-v2.hidden]
        [:a#download-as-json-v2.hidden]
+       [:a#download-as-roam-json.hidden]
        [:a#download-as-html.hidden]
        [:a#download-as-zip.hidden]
        [:a#export-as-markdown.hidden]

+ 2 - 0
src/main/frontend/dicts.cljs

@@ -250,6 +250,7 @@
         :export-opml "Export as OPML"
         :export-public-pages "Export public pages"
         :export-json "Export as JSON"
+        :export-roam-json "Export as Roam JSON"
         :export-edn "Export as EDN"
         :export-datascript-edn "Export datascript EDN"
         :convert-markdown "Convert Markdown headings to unordered lists (# -> -)"
@@ -894,6 +895,7 @@
            :cancel "取消"
            :re-index "重新建立索引"
            :export-json "以 JSON 格式导出"
+           :export-roam-json "以 Roam JSON 格式导出"
            :export-markdown "以 Markdown 格式导出"
            :export-opml "以 OPML 格式导出"
            :convert-markdown "转换 Markdown 格式(Unordered list 或 Heading)"

+ 84 - 0
src/main/frontend/external/roam_export.cljs

@@ -0,0 +1,84 @@
+(ns frontend.external.roam-export
+  (:require [clojure.string :as str]
+            [clojure.set :as s]
+            [datascript.core :as d]
+            [frontend.state :as state]
+
+            [frontend.db :as db]
+            [clojure.walk :as walk]))
+
+(def todo-marker-regex
+  #"^(NOW|LATER|TODO|DOING|WAITING|WAIT|CANCELED|CANCELLED|STARTED|IN-PROGRESS)")
+
+(def done-marker-regex #"^DONE")
+
+(def nano-char-range "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+(defn- nano-id-char []
+  (rand-nth nano-char-range))
+
+(defn nano-id []
+  (->> (repeatedly 9 nano-id-char)
+       (str/join)))
+
+(defn uuid->uid-map []
+  (let [conn (db/get-conn (state/get-current-repo))]
+    (->> conn
+     (d/q '[:find (pull ?r [:block/uuid])
+            :in $
+            :where
+            [?b :block/refs ?r]])
+     (map (comp :block/uuid first))
+     (distinct)
+     (map (fn [uuid] [uuid (nano-id)]))
+     (into {}))))
+
+(defn update-content [content uuid->uid-map]
+  (let [uuids (keys uuid->uid-map)]
+    (reduce
+     (fn [acc uuid]
+       (if (str/includes? acc (str uuid))
+         (str/replace acc (str uuid) (get uuid->uid-map uuid))
+         acc))
+     content
+     uuids)))
+
+(defn update-uid [{:block/keys [uuid content] :as b}
+                  uuid->uid-map]
+  (cond-> b
+    (contains? uuid->uid-map uuid)
+    (assoc :block/uid (get uuid->uid-map uuid))
+
+    (some (fn [id] (str/includes? (str content) (str id))) (keys uuid->uid-map))
+    (update :block/content #(update-content % uuid->uid-map))))
+
+(defn update-todo [{:block/keys [content] :as block}]
+  (if content
+    (update block :block/content
+            (fn [c]
+              (-> c
+                  (str/replace todo-marker-regex "{{[[TODO]]}}")
+                  (str/replace done-marker-regex "{{[[DONE]]}}"))))
+    block))
+
+(defn traverse
+  [keyseq vec-tree]
+  (let [uuid->uid-map (uuid->uid-map)]
+    (walk/postwalk
+     (fn [x]
+       (cond
+         (and (map? x) (contains? x :block/uuid))
+         (-> x
+
+             (update-uid uuid->uid-map)
+
+             (update-todo)
+
+             (s/rename-keys {:block/original-name :page/title
+                             :block/content :block/string})
+
+             (select-keys keyseq))
+
+         :else
+         x))
+     vec-tree)))

+ 44 - 0
src/main/frontend/handler/export.cljs

@@ -14,6 +14,7 @@
             [frontend.handler.file :as file-handler]
             [frontend.modules.file.core :as outliner-file]
             [frontend.modules.outliner.tree :as outliner-tree]
+            [frontend.external.roam-export :as roam-export]
             [frontend.publishing.html :as html]
             [frontend.state :as state]
             [frontend.util :as util]
@@ -660,3 +661,46 @@
         (.setAttribute anchor "href" data-str)
         (.setAttribute anchor "download" (file-name repo :json))
         (.click anchor)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Export to roam json ;;
+;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; https://roamresearch.com/#/app/help/page/Nxz8u0vXU
+;; export to roam json according to above spec
+(defn- roam-json [conn]
+  (->> (d/q '[:find (pull ?b [*])
+              :in $
+              :where
+              [?b :block/file]
+              [?b :block/original-name]
+              [?b :block/name]] conn)
+
+       (map (fn [[{:block/keys [name] :as page}]]
+              (assoc page
+                     :block/children
+                     (outliner-tree/blocks->vec-tree
+                      (db/get-page-blocks-no-cache
+                       (state/get-current-repo)
+                       name
+                       {:transform? false}) name))))
+
+       (roam-export/traverse
+        [:page/title
+         :block/string
+         :block/uid
+         :block/children])))
+
+(defn export-repo-as-roam-json!
+  [repo]
+  (when-let [conn (db/get-conn repo)]
+    (let [json-str
+          (-> (roam-json conn)
+              clj->js
+              js/JSON.stringify)
+          data-str (str "data:text/json;charset=utf-8,"
+                        (js/encodeURIComponent json-str))]
+      (when-let [anchor (gdom/getElement "download-as-roam-json")]
+        (.setAttribute anchor "href" data-str)
+        (.setAttribute anchor "download" (file-name (str repo "_roam") :json))
+        (.click anchor)))))