feat(outliner): sync to file

pull/1656/head
defclass 2021-04-01 12:19:26 +08:00
parent 2f790d3a34
commit 3b8a825b24
9 changed files with 310 additions and 56 deletions

View File

@ -281,6 +281,12 @@
@conn) @conn)
(flatten)))) (flatten))))
(defn get-file-by-path
[file-path]
(when-let [repo (state/get-current-repo)]
(when-let [conn (conn/get-files-conn repo)]
(d/pull @conn '[*] [:file/path file-path]))))
(defn get-custom-css (defn get-custom-css
[] []
(when-let [repo (state/get-current-repo)] (when-let [repo (state/get-current-repo)]
@ -638,6 +644,18 @@
block-uuid) block-uuid)
sort-by-left))) sort-by-left)))
(defn get-blocks-by-page
[id-or-lookup-ref]
(when-let [conn (conn/get-conn)]
(->
(d/q
'[:find (pull ?block [*])
:in $ ?page
:where
[?block :block/page ?page]]
conn id-or-lookup-ref)
flatten)))
(defn get-block-children (defn get-block-children
"Including nested children." "Including nested children."
[repo block-uuid] [repo block-uuid]

View File

@ -2,7 +2,8 @@
(:require [datascript.core :as d] (:require [datascript.core :as d]
[frontend.db.react :as react] [frontend.db.react :as react]
[frontend.util :as util] [frontend.util :as util]
[frontend.debug :as debug])) [frontend.debug :as debug]
[frontend.db.utils :as db-utils]))
(defn get-by-id (defn get-by-id
[conn id] [conn id]
@ -29,8 +30,10 @@
(defn save-block (defn save-block
[conn block-m] [conn block-m]
(let [tx (-> (dissoc block-m :block/children) (let [tx (-> (dissoc block-m :block/children)
(util/remove-nils))] (util/remove-nils))
(d/transact! conn [tx]))) lookup-ref (:block/uuid block-m)]
(d/transact! conn [tx])
(db-utils/pull [:block/uuid lookup-ref])))
(defn del-block (defn del-block
[conn id-or-look-ref] [conn id-or-look-ref]

View File

@ -12,7 +12,9 @@
[cljs-time.coerce :as tc] [cljs-time.coerce :as tc]
[cljs-time.core :as t] [cljs-time.core :as t]
[frontend.modules.outliner.core :as outliner-core] [frontend.modules.outliner.core :as outliner-core]
[frontend.modules.outliner.tree :as tree])) [frontend.modules.outliner.tree :as tree]
[lambdaisland.glogi :as log]
[frontend.debug :as debug]))
(defn- remove-block-child! (defn- remove-block-child!
[target-block parent-block] [target-block parent-block]
@ -367,22 +369,25 @@
2. Sometimes we might need to move a parent block to it's own child. 2. Sometimes we might need to move a parent block to it's own child.
" "
[current-block target-block top? nested?] [current-block target-block top? nested?]
(let [[current-node target-node] (if-not (every? map? [current-block target-block])
(mapv outliner-core/block [current-block target-block])] (log/error :dnd/move-block-argument-error
(cond {:current-block current-block :target-block target-block})
top? (let [[current-node target-node]
(let [first-child? (mapv outliner-core/block [current-block target-block])]
(= (tree/-get-parent-id target-node) (cond
(tree/-get-left-id target-node))] top?
(if first-child? (let [first-child?
(let [parent (tree/-get-parent target-node)] (= (tree/-get-parent-id target-node)
(outliner-core/move-subtree current-node parent false)) (tree/-get-left-id target-node))]
(outliner-core/move-subtree current-node target-node true))) (if first-child?
nested? (let [parent (tree/-get-parent target-node)]
(outliner-core/move-subtree current-node target-node false) (outliner-core/move-subtree current-node parent false))
(outliner-core/move-subtree current-node target-node true)))
nested?
(outliner-core/move-subtree current-node target-node false)
:else :else
(outliner-core/move-subtree current-node target-node true)) (outliner-core/move-subtree current-node target-node true))
(let [repo (state/get-current-repo)] (let [repo (state/get-current-repo)]
(db/refresh repo {:key :block/change (db/refresh repo {:key :block/change
:data [(:data current-node) (:data target-node)]})))) :data [(:data current-node) (:data target-node)]})))))

View File

@ -561,7 +561,7 @@
(-> (->
(wrap-parse-block block) (wrap-parse-block block)
(outliner-core/block) (outliner-core/block)
(tree/-save)) (outliner-core/save-node))
(let [opts {:key :block/change (let [opts {:key :block/change
:data [block]}] :data [block]}]
(db/refresh repo opts)))) (db/refresh repo opts))))
@ -768,7 +768,7 @@
(mapv outliner-core/block [current-block new-block]) (mapv outliner-core/block [current-block new-block])
has-children? (seq (tree/-get-children current-node)) has-children? (seq (tree/-get-children current-node))
new-is-sibling? (not has-children?)] new-is-sibling? (not has-children?)]
(tree/-save current-node) (outliner-core/save-node current-node)
(outliner-core/insert-node new-node current-node new-is-sibling?))) (outliner-core/insert-node new-node current-node new-is-sibling?)))

View File

@ -0,0 +1,64 @@
(ns frontend.modules.file.core
(:require [frontend.debug :as debug]
[clojure.string :as str]
[frontend.state :as state]
[cljs.core.async :as async]
[frontend.db.conn :as conn]
[frontend.db.utils :as db-utils]
[frontend.db.model :as model]
[frontend.modules.outliner.tree :as tree]))
(defn clip-content
[content]
(->
(str/replace content #"^\n+" "")
(str/replace #"^#+" "")
(str/replace #"\n+$" "")))
(defn transform-content
[pre-block? content level]
(if pre-block?
(clip-content content)
(let [prefix (->>
(repeat level "#")
(apply str))
new-content (clip-content content)]
(str prefix " " new-content))))
(defn tree->file-content
[tree init-level]
(loop [block-contents []
[f & r] tree
level init-level]
(if (nil? f)
(str/join "\n" block-contents)
(let [content (transform-content
(:block/pre-block? f) (:block/content f) level)
new-content
(if-let [children (seq (:block/children f))]
[content (tree->file-content children (inc level))]
[content])]
(recur (into block-contents new-content) r level)))))
(def markdown-init-level 2)
(defn push-to-write-chan
[files file->content & opts]
(let [repo (state/get-current-repo)]
(when-let [chan (state/get-file-write-chan)]
(let [chan-callback (:chan-callback opts)]
(async/put! chan [repo files opts file->content])
(when chan-callback
(chan-callback))))))
(defn save-tree
[page-block tree]
{:pre [(map? page-block)]}
(let [new-content (tree->file-content tree markdown-init-level)
file-db-id (-> page-block :block/file :db/id)
file-path (-> (db-utils/entity file-db-id) :file/path)
_ (assert (string? file-path) "File path should satisfy string?")
{old-content :file/content :as file} (model/get-file-by-path file-path)
files [[file-path new-content]]
file->content {file-path old-content}]
(push-to-write-chan files file->content)))

View File

@ -7,12 +7,15 @@
[frontend.modules.outliner.ref :as outliner-ref] [frontend.modules.outliner.ref :as outliner-ref]
[frontend.state :as state] [frontend.state :as state]
[frontend.debug :as debug] [frontend.debug :as debug]
[clojure.set :as set])) [clojure.set :as set]
[frontend.modules.outliner.file :as outliner-file]
[frontend.util :as util]))
(defrecord Block [data]) (defrecord Block [data])
(defn block (defn block
[m] [m]
(assert (map? m) (util/format "block data must be map,got: %s %s" (type m) m))
(->Block m)) (->Block m))
(defn get-block-by-id (defn get-block-by-id
@ -97,9 +100,9 @@
(-save [this] (-save [this]
(let [conn (conn/get-conn false) (let [conn (conn/get-conn false)
data (:data this)] data (:data this)
(db-outliner/save-block conn data) new-data (db-outliner/save-block conn data)]
this)) (assoc this :data new-data)))
(-del [this] (-del [this]
(let [conn (conn/get-conn false) (let [conn (conn/get-conn false)
@ -126,44 +129,51 @@
(throw (js/Error. "Number of children and sorted-children are not equal.")))) (throw (js/Error. "Number of children and sorted-children are not equal."))))
sorted-children))))))))) sorted-children)))))))))
(defn save-node
[node]
{:pre [(tree/satisfied-inode? node)]}
(let [saved-node (tree/-save node)]
(outliner-file/sync-to-file saved-node)))
(defn insert-node-as-first-child (defn insert-node-as-first-child
"Insert a node as first child." "Insert a node as first child."
[new-node parent-node] [new-node parent-node]
(:pre [(every? tree/satisfied-inode? [new-node parent-node])]) {:pre [(every? tree/satisfied-inode? [new-node parent-node])]}
(let [parent-id (tree/-get-id parent-node) (let [parent-id (tree/-get-id parent-node)
node (-> (tree/-set-left-id new-node parent-id) node (-> (tree/-set-left-id new-node parent-id)
(tree/-set-parent-id parent-id)) (tree/-set-parent-id parent-id))
right-node (tree/-get-down parent-node)] right-node (tree/-get-down parent-node)]
(do (do
(if (tree/satisfied-inode? right-node) (if (tree/satisfied-inode? right-node)
(let [new-right-node (tree/-set-left-id right-node (tree/-get-id new-node))] (let [new-right-node (tree/-set-left-id right-node (tree/-get-id new-node))
(tree/-save node) saved-new-node (tree/-save node)]
(tree/-save new-right-node)) (tree/-save new-right-node)
(tree/-save node)) saved-new-node)
(let [repo (state/get-current-repo)] (tree/-save node)))))
(outliner-state/update-block-state repo node)))))
(defn insert-node-as-sibling (defn insert-node-as-sibling
"Insert a node as sibling." "Insert a node as sibling."
[new-node left-node] [new-node left-node]
(:pre [(every? tree/satisfied-inode? [new-node left-node])]) {:pre [(every? tree/satisfied-inode? [new-node left-node])]}
(let [node (-> (tree/-set-left-id new-node (tree/-get-id left-node)) (let [node (-> (tree/-set-left-id new-node (tree/-get-id left-node))
(tree/-set-parent-id (tree/-get-parent-id left-node))) (tree/-set-parent-id (tree/-get-parent-id left-node)))
right-node (tree/-get-right left-node)] right-node (tree/-get-right left-node)]
(do (do
(if (tree/satisfied-inode? right-node) (if (tree/satisfied-inode? right-node)
(let [new-right-node (tree/-set-left-id right-node (tree/-get-id new-node))] (let [new-right-node (tree/-set-left-id right-node (tree/-get-id new-node))
(tree/-save node) saved-new-node (tree/-save node)]
(tree/-save new-right-node)) (tree/-save new-right-node)
(tree/-save node)) saved-new-node)
(let [repo (state/get-current-repo)] (tree/-save node)))))
(outliner-state/update-block-state repo node)))))
(defn insert-node (defn insert-node
[new-node target-node sibling?] [new-node target-node sibling?]
(if sibling? (let [saved-node
(insert-node-as-sibling new-node target-node) (if sibling?
(insert-node-as-first-child new-node target-node))) (insert-node-as-sibling new-node target-node)
(insert-node-as-first-child new-node target-node))]
(outliner-file/sync-to-file saved-node)))
(defn delete-node (defn delete-node
"Delete node from the tree." "Delete node from the tree."
@ -175,8 +185,7 @@
(let [left-node (tree/-get-left node) (let [left-node (tree/-get-left node)
new-right-node (tree/-set-left-id right-node (tree/-get-id left-node))] new-right-node (tree/-set-left-id right-node (tree/-get-id left-node))]
(tree/-save new-right-node))) (tree/-save new-right-node)))
(let [repo (state/get-current-repo)] (outliner-file/sync-to-file node)))
(outliner-state/update-block-state repo node))))
(defn move-subtree (defn move-subtree
"Move subtree to a destination position in the relation tree. "Move subtree to a destination position in the relation tree.
@ -194,4 +203,10 @@
(tree/-save new-right-node))) (tree/-save new-right-node)))
(if sibling (if sibling
(insert-node-as-sibling root target-node) (insert-node-as-sibling root target-node)
(insert-node-as-first-child root target-node)))) (insert-node-as-first-child root target-node))
(do
(outliner-file/sync-to-file root)
;; TODO Should sync to file where the target-node located when the
;; target-node is in another file.
)))

View File

@ -0,0 +1,16 @@
(ns frontend.modules.outliner.file
(:require [frontend.state :as state]
[frontend.db.model :as model]
[frontend.modules.outliner.tree :as tree]
[frontend.modules.file.core :as file]
[frontend.debug :as debug]
[frontend.db.utils :as db-utils]))
(defn sync-to-file
[updated-block]
{:pre [(tree/satisfied-inode? updated-block)]}
(let [page-db-id (-> updated-block :data :block/page :db/id)
blocks (model/get-blocks-by-page page-db-id)
tree (tree/blocks->vec-tree blocks)
page-block (db-utils/pull page-db-id)]
(file/save-tree page-block tree)))

View File

@ -1,7 +1,8 @@
(ns frontend.modules.outliner.tree (ns frontend.modules.outliner.tree
(:require [frontend.modules.outliner.ref :as outliner-ref] (:require [frontend.modules.outliner.ref :as outliner-ref]
[clojure.set :as set] [clojure.set :as set]
[frontend.modules.outliner.utils :as outliner-u])) [frontend.modules.outliner.utils :as outliner-u]
[frontend.debug :as debug]))
(defprotocol INode (defprotocol INode
(-get-id [this]) (-get-id [this])
@ -54,16 +55,16 @@
(recur db-id (conj children child)) (recur db-id (conj children child))
children))) children)))
(comment (defn- clip-block
(defn- clip-block "For debug. It's should be removed."
"For debug. It's should be removed." [x]
[x] (let [ks [:block/parent :block/left :block/pre-block? :block/uuid
(let [ks [:block/parent :block/left :block/pre-block? :block/uuid :block/level :block/title :db/id :block/page]]
:block/level :block/title :db/id]] (map #(select-keys % ks) x)))
(map #(select-keys % ks) x))))
(defn blocks->vec-tree (defn blocks->vec-tree
[blocks] [blocks]
;; (debug/pprint "blocks->vec-tree" blocks)
(let [{:keys [ids parents indexed-by-position]} (let [{:keys [ids parents indexed-by-position]}
(prepare-blocks blocks) (prepare-blocks blocks)
root-id (first (set/difference parents ids))] root-id (first (set/difference parents ids))]

View File

@ -0,0 +1,132 @@
(ns frontend.modules.file.core-test
(:require [cljs.test :refer [deftest is are testing use-fixtures run-tests] :as test]
[cljs-run-test :refer [run-test]]
[frontend.modules.file.core :as file]
[clojure.string :as str]))
(deftest test-transform-content
(let [s "#### abc\n\n"
r "abc"]
(is (= r (file/clip-content s)))))
(def tree
'({:block/pre-block? true,
:block/uuid #uuid "60643869-3b5b-4e1b-a1f8-28ec965abb4c",
:block/left {:db/id 20},
:block/body
({:block/uuid #uuid "60643869-6feb-4b4a-87b0-7a8a76cf2746",
:block/refs (),
:block/anchor "level_1",
:block/children
#{[:block/uuid #uuid "60643869-c9dc-4a06-b1c3-e86bad631b3a"]},
:block/body [],
:block/meta
{:timestamps [], :properties [], :start-pos 30, :end-pos 41},
:block/level 2,
:block/tags [],
:block/title [["Plain" "level 1"]]}
{:block/uuid #uuid "60643869-c9dc-4a06-b1c3-e86bad631b3a",
:block/refs (),
:block/anchor "level_1_1",
:block/children
#{[:block/uuid #uuid "60643869-3d80-4926-8854-5de911fb2aca"]},
:block/body [],
:block/meta
{:timestamps [], :properties [], :start-pos 41, :end-pos 55},
:block/level 3,
:block/tags [],
:block/title [["Plain" "level 1-1"]]}
{:block/uuid #uuid "60643869-3d80-4926-8854-5de911fb2aca",
:block/refs (),
:block/anchor "level_1_1_1",
:block/children #{},
:block/body [],
:block/meta
{:timestamps [], :properties [], :start-pos 55, :end-pos 72},
:block/level 4,
:block/tags [],
:block/title [["Plain" "level 1-1-1"]]}
{:block/uuid #uuid "60643869-39d5-497e-b300-fa49993f6fda",
:block/refs (),
:block/anchor "level_2",
:block/children #{},
:block/body [],
:block/meta
{:timestamps [], :properties [], :start-pos 72, :end-pos 83},
:block/level 2,
:block/tags [],
:block/title [["Plain" "level 2"]]}),
:block/format :markdown,
:block/level 2,
:block/refs-with-children (),
:block/content "---\ntitle: Mar 31th, 2021\n---\n",
:db/id 24,
:block/parent {:db/id 20},
:block/page {:db/id 20},
:block/file {:db/id 16}}
{:block/uuid #uuid "60643869-6feb-4b4a-87b0-7a8a76cf2746",
:block/left {:db/id 24},
:block/anchor "level_1",
:block/children
({:block/uuid #uuid "60643869-c9dc-4a06-b1c3-e86bad631b3a",
:block/left {:db/id 25},
:block/anchor "level_1_1",
:block/children
({:block/uuid #uuid "60643869-3d80-4926-8854-5de911fb2aca",
:block/left {:db/id 26},
:block/anchor "level_1_1_1",
:block/body [],
:block/format :markdown,
:block/level 4,
:block/title [["Plain" "level 1-1-1"]],
:block/refs-with-children (),
:block/content "#### level 1-1-1\n",
:db/id 27,
:block/parent {:db/id 26},
:block/page {:db/id 20},
:block/file {:db/id 16}}),
:block/body [],
:block/format :markdown,
:block/level 3,
:block/title [["Plain" "level 1-1"]],
:block/refs-with-children (),
:block/content "### level 1-1",
:db/id 26,
:block/parent {:db/id 25},
:block/page {:db/id 20},
:block/file {:db/id 16}}),
:block/body [],
:block/format :markdown,
:block/level 2,
:block/title [["Plain" "level 1"]],
:block/refs-with-children (),
:block/content "## level 1\n",
:db/id 25,
:block/parent {:db/id 20},
:block/page {:db/id 20},
:block/file {:db/id 16}}
{:block/uuid #uuid "60643869-39d5-497e-b300-fa49993f6fda",
:block/left {:db/id 25},
:block/anchor "level_2",
:block/repo "logseq_local_test_navtive_fs",
:block/body [],
:block/format :markdown,
:block/level 2,
:block/title [["Plain" "level 2"]],
:block/refs-with-children (),
:block/content "## level 2",
:db/id 28,
:block/parent {:db/id 20},
:block/page {:db/id 20},
:block/file {:db/id 16}}))
(defn- clip-first-space [s]
(str/replace s #"\n\s+" "\n"))
(deftest test-tree->file-content
(let [r "---\ntitle: Mar 31th, 2021\n---\n## level 1\n### level 1-1\n#### level 1-1-1\n## level 2"
r (clip-first-space r)]
(is (= r (file/tree->file-content tree 2)))))
(comment
(run-test test-tree->file-content))