mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
Merge pull request #12130 from logseq/refactor/plugin-api-properties
refactor: Plugin apis
This commit is contained in:
14
AGENTS.md
14
AGENTS.md
@@ -2,16 +2,20 @@
|
||||
- Use clojure-mcp `clojure_inspect_project` to get project structure.
|
||||
- `src/`: Core source code
|
||||
- `src/main/`: The core logic of the application
|
||||
- `src/main/mobile/`: Mobile app code
|
||||
- `src/main/frontend/inference_worker/`: Code running in a webworker for text-embedding and vector-search
|
||||
- `src/main/frontend/worker/`: Code running in an another webworker
|
||||
- `src/main/frontend/worker/rtc/`: RTC(Real Time Collaboration) related code
|
||||
- `src/main/frontend/components/`: UI components
|
||||
- `src/main/mobile/`: Mobile app code
|
||||
- `src/main/frontend/inference_worker/`: Code running in a webworker for text-embedding and vector-search
|
||||
- `src/main/frontend/worker/`: Code running in an another webworker
|
||||
- `src/main/frontend/worker/rtc/`: RTC(Real Time Collaboration) related code
|
||||
- `src/main/frontend/components/`: UI components
|
||||
- `src/electron/`: Code specifically for the Electron desktop application.
|
||||
- `src/test/`: unit-tests
|
||||
- `deps/`: Internal dependencies/modules
|
||||
- `clj-e2e/`: End to end test code
|
||||
|
||||
## Common used cljs keywords
|
||||
- All commonly used ClojureScript keywords are defined using `logseq.common.defkeywords/defkeyword`.
|
||||
- Search for `defkeywords` to find all the definitions.
|
||||
|
||||
## Testing Commands
|
||||
- Run linters and unit-tests: `bb dev:lint-and-test`
|
||||
- Run single focused unit-test:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns logseq.e2e.plugins-basic-test
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[clojure.test :refer [deftest testing is use-fixtures]]
|
||||
[jsonista.core :as json]
|
||||
@@ -14,6 +15,10 @@
|
||||
(use-fixtures :once fixtures/open-page)
|
||||
(use-fixtures :each fixtures/new-logseq-page)
|
||||
|
||||
(defn ->plugin-ident
|
||||
[property-name]
|
||||
(str ":plugin.property._test_plugin/" property-name))
|
||||
|
||||
(defn- to-snake-case
|
||||
"Converts a string to snake_case. Handles camelCase, PascalCase, spaces, hyphens, and existing underscores.
|
||||
Examples:
|
||||
@@ -31,9 +36,14 @@
|
||||
;; Remove redundant underscores and trim
|
||||
(clojure.string/replace #"_+" "_")
|
||||
(clojure.string/trim)
|
||||
;; Convert to lowercase
|
||||
;; Convert to lowercase
|
||||
(clojure.string/lower-case))))
|
||||
|
||||
(defonce ^:private *property-idx (atom 0))
|
||||
(defn- new-property
|
||||
[]
|
||||
(str "p" (swap! *property-idx inc)))
|
||||
|
||||
(defn- ls-api-call!
|
||||
[tag & args]
|
||||
(let [tag (name tag)
|
||||
@@ -43,10 +53,10 @@
|
||||
ns1 (string/lower-case (if (and ns? (not inbuilt?))
|
||||
(str "sdk." (first ns')) "api"))
|
||||
name1 (if ns? (to-snake-case (last ns')) tag)
|
||||
estr (format "s => { const args = JSON.parse(s);const o=logseq.%1$s; return o['%2$s']?.apply(null, args || []); }" ns1 name1)]
|
||||
(let [args (json/write-value-as-string (vec args))]
|
||||
;(prn "Debug: eval-js #" estr args)
|
||||
(w/eval-js estr args))))
|
||||
estr (format "s => { const args = JSON.parse(s);const o=logseq.%1$s; return o['%2$s']?.apply(null, args || []); }" ns1 name1)
|
||||
args (json/write-value-as-string (vec args))]
|
||||
;; (prn "Debug: eval-js #" estr args)
|
||||
(w/eval-js estr args)))
|
||||
|
||||
(defn- assert-api-ls-block!
|
||||
([ret] (assert-api-ls-block! ret 1))
|
||||
@@ -56,22 +66,23 @@
|
||||
(assert/assert-have-count (str "#ls-block-" uuid') count)
|
||||
uuid')))
|
||||
|
||||
(deftest apis-related-test
|
||||
(testing "block related apis"
|
||||
(deftest editor-apis-test
|
||||
(testing "editor related apis"
|
||||
(page/new-page "test-block-apis")
|
||||
(ls-api-call! :ui.showMsg "hello world" "info")
|
||||
(let [ret (ls-api-call! :editor.appendBlockInPage "test-block-apis" "append-block-in-page-0")
|
||||
ret1 (ls-api-call! :editor.appendBlockInPage "append-block-in-current-page-0")
|
||||
_ (assert-api-ls-block! ret1)
|
||||
uuid' (assert-api-ls-block! ret)]
|
||||
(assert-api-ls-block! ret1)
|
||||
(-> (ls-api-call! :editor.insertBlock uuid' "insert-0")
|
||||
(assert-api-ls-block!))
|
||||
(ls-api-call! :editor.updateBlock uuid' "append-but-updated-0")
|
||||
(k/esc)
|
||||
(w/wait-for ".block-title-wrap:text('append-but-updated-0')")
|
||||
(ls-api-call! :editor.removeBlock uuid')
|
||||
(assert-api-ls-block! uuid' 0)))
|
||||
(assert-api-ls-block! uuid' 0))))
|
||||
|
||||
(deftest block-properties-test
|
||||
(testing "block properties related apis"
|
||||
(page/new-page "test-block-properties-apis")
|
||||
(let [ret (ls-api-call! :editor.appendBlockInPage "test-block-properties-apis" "block-in-page-0" {:properties {:p1 1}})
|
||||
@@ -81,8 +92,8 @@
|
||||
props2 (ls-api-call! :editor.getPageProperties "test-block-properties-apis")]
|
||||
(w/wait-for ".property-k:text('p1')")
|
||||
(is (= 1 (get prop1 "value")))
|
||||
(is (= (get prop1 "ident") ":plugin.property._api/p1"))
|
||||
(is (= 1 (get props1 ":plugin.property._api/p1")))
|
||||
(is (= (get prop1 "ident") ":plugin.property._test_plugin/p1"))
|
||||
(is (= 1 (get props1 ":plugin.property._test_plugin/p1")))
|
||||
(is (= ["Page"] (get props2 ":block/tags")))
|
||||
(ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2")
|
||||
(ls-api-call! :editor.upsertBlockProperty uuid' "p3" true)
|
||||
@@ -102,20 +113,226 @@
|
||||
(ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2-updated")
|
||||
(w/wait-for ".block-title-wrap:text('p2-updated')")
|
||||
(let [props (ls-api-call! :editor.getBlockProperties uuid')]
|
||||
(is (= (get props ":plugin.property._api/p3") false))
|
||||
(is (= (get props ":plugin.property._api/p2") "p2-updated")))))
|
||||
(is (= (get props ":plugin.property._test_plugin/p3") false))
|
||||
(is (= (get props ":plugin.property._test_plugin/p2") "p2-updated"))))))
|
||||
|
||||
(deftest property-upsert-test
|
||||
(testing "property with default settings"
|
||||
(let [p (new-property)]
|
||||
(ls-api-call! :editor.upsertProperty p)
|
||||
(let [property (ls-api-call! :editor.getProperty p)]
|
||||
(is (= "default" (get property "type")))
|
||||
(is (= ":db.cardinality/one" (get property "cardinality"))))))
|
||||
(testing "property with specified cardinality && type"
|
||||
(let [p (new-property)]
|
||||
(ls-api-call! :editor.upsertProperty p {:type "number"
|
||||
:cardinality "one"})
|
||||
(let [property (ls-api-call! :editor.getProperty p)]
|
||||
(is (= "number" (get property "type")))
|
||||
(is (= ":db.cardinality/one" (get property "cardinality")))))
|
||||
(let [p (new-property)]
|
||||
(ls-api-call! :editor.upsertProperty p {:type "number"
|
||||
:cardinality "many"})
|
||||
(let [property (ls-api-call! :editor.getProperty p)]
|
||||
(is (= "number" (get property "type")))
|
||||
(is (= ":db.cardinality/many" (get property "cardinality"))))
|
||||
(ls-api-call! :editor.upsertProperty p {:type "default"})
|
||||
(let [property (ls-api-call! :editor.getProperty p)]
|
||||
(is (= "default" (get property "type"))))))
|
||||
;; TODO: How to test against eval-js errors on playwright?
|
||||
#_(testing ":checkbox property doesn't allow :many cardinality"
|
||||
(let [p (new-property)]
|
||||
(ls-api-call! :editor.upsertProperty p {:type "checkbox"
|
||||
:cardinality "many"}))))
|
||||
|
||||
(deftest property-related-test
|
||||
(testing "properties management related apis"
|
||||
(let [_ (ls-api-call! :editor.upsertProperty "o1")
|
||||
_ (ls-api-call! :editor.upsertProperty "o2" {:type "number"})
|
||||
_ (ls-api-call! :editor.upsertProperty "user.property/o3" {:type "node"})
|
||||
prop1 (ls-api-call! :editor.getProperty "o1")
|
||||
prop2 (ls-api-call! :editor.getProperty "o2")
|
||||
prop3 (ls-api-call! :editor.getProperty "user.property/o3")]
|
||||
(is (= (get prop1 "ident") ":plugin.property._api/o1"))
|
||||
(is (= (get prop1 "type") "default"))
|
||||
(is (= (get prop2 "type") "number"))
|
||||
(is (= (get prop3 "ident") ":user.property/o3"))
|
||||
(is (= (get prop3 "type") "node"))
|
||||
(ls-api-call! :editor.removeProperty "o2")
|
||||
(is (nil? (w/find-one-by-text ".property-k" "o2"))))))
|
||||
(dorun
|
||||
(map-indexed
|
||||
(fn [idx property-type]
|
||||
(let [property-name (str "p" idx)
|
||||
_ (ls-api-call! :editor.upsertProperty property-name {:type property-type})
|
||||
property (ls-api-call! :editor.getProperty property-name)]
|
||||
(is (= (get property "ident") (str ":plugin.property._test_plugin/" property-name)))
|
||||
(is (= (get property "type") property-type))
|
||||
(ls-api-call! :editor.removeProperty property-name)
|
||||
(is (nil? (ls-api-call! :editor.getProperty property-name)))))
|
||||
["default" "number" "date" "datetime" "checkbox" "url" "node" "json" "string"]))))
|
||||
|
||||
(deftest insert-block-with-properties
|
||||
(testing "insert block with properties"
|
||||
(let [page "insert-block-properties-test"
|
||||
_ (page/new-page page)
|
||||
;; :checkbox, :number, :url, :json can be inferred and default to :default, but not for :page
|
||||
b1 (ls-api-call! :editor.insertBlock page "b1" {:properties {"x1" true
|
||||
"x2" "https://logseq.com"
|
||||
"x3" 1
|
||||
"x4" [1]
|
||||
"x5" {:foo "bar"}
|
||||
"x6" "Page x"
|
||||
"x7" ["Page y" "Page z"]
|
||||
"x8" "some content"}
|
||||
:schema {"x6" {:type "page"}
|
||||
"x7" {:type "page"}}})]
|
||||
(is (true? (get b1 (->plugin-ident "x1"))))
|
||||
(is (= "https://logseq.com" (-> (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x2")))
|
||||
(get "title"))))
|
||||
(is (= 1 (-> (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x3")))
|
||||
(get ":logseq.property/value"))))
|
||||
(is (= 1 (-> (ls-api-call! :editor.getBlock (first (get b1 (->plugin-ident "x4"))))
|
||||
(get ":logseq.property/value"))))
|
||||
(is (= "{\"foo\":\"bar\"}" (get b1 (->plugin-ident "x5"))))
|
||||
(let [page-x (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x6")))]
|
||||
(is (= "page x" (get page-x "name"))))
|
||||
(is (= ["page y" "page z"] (map #(-> (ls-api-call! :editor.getBlock %)
|
||||
(get "name")) (get b1 (->plugin-ident "x7")))))
|
||||
(let [x8-block-value (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x8")))]
|
||||
(is (= "some content" (get x8-block-value "title")))
|
||||
(is (some? (get x8-block-value "page")))))))
|
||||
|
||||
(deftest update-block-with-properties
|
||||
(testing "update block with properties"
|
||||
(let [page "update-block-properties-test"
|
||||
_ (page/new-page page)
|
||||
block (ls-api-call! :editor.insertBlock page "b1")
|
||||
_ (ls-api-call! :editor.updateBlock (get block "uuid")
|
||||
"b1-new-content"
|
||||
{:properties {"y1" true
|
||||
"y2" "https://logseq.com"
|
||||
"y3" 1
|
||||
"y4" [1]
|
||||
"y5" {:foo "bar"}
|
||||
"y6" "Page x"
|
||||
"y7" ["Page y" "Page z"]
|
||||
"y8" "some content"}
|
||||
:schema {"y6" {:type "page"}
|
||||
"y7" {:type "page"}}})
|
||||
b1 (ls-api-call! :editor.getBlock (get block "uuid"))]
|
||||
(is (true? (get b1 (->plugin-ident "y1"))))
|
||||
(is (= "https://logseq.com" (-> (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y2") "id"]))
|
||||
(get "title"))))
|
||||
(is (= 1 (-> (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y3") "id"]))
|
||||
(get ":logseq.property/value"))))
|
||||
(is (= 1 (-> (ls-api-call! :editor.getBlock (get (first (get b1 (->plugin-ident "y4"))) "id"))
|
||||
(get ":logseq.property/value"))))
|
||||
(is (= "{\"foo\":\"bar\"}" (get b1 (->plugin-ident "y5"))))
|
||||
(let [page-x (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y6") "id"]))]
|
||||
(is (= "page x" (get page-x "name"))))
|
||||
(is (= ["page y" "page z"] (map #(-> (ls-api-call! :editor.getBlock %)
|
||||
(get "name"))
|
||||
(map #(get % "id") (get b1 (->plugin-ident "y7"))))))
|
||||
(let [y8-block-value (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y8") "id"]))]
|
||||
(is (= "some content" (get y8-block-value "title")))
|
||||
(is (some? (get y8-block-value "page")))))))
|
||||
|
||||
(deftest insert-batch-blocks-test
|
||||
(testing "insert batch blocks"
|
||||
(let [page "insert batch blocks"
|
||||
_ (page/new-page page)
|
||||
page-uuid (get (ls-api-call! :editor.getBlock page) "uuid")
|
||||
result (ls-api-call! :editor.insertBatchBlock page-uuid
|
||||
[{:content "b1"
|
||||
:children [{:content "b1.1"
|
||||
:children [{:content "b1.1.1"}
|
||||
{:content "b1.1.2"}]}
|
||||
{:content "b1.2"}]}
|
||||
{:content "b2"}])
|
||||
contents (util/get-page-blocks-contents)]
|
||||
(is (= contents ["b1" "b1.1" "b1.1.1" "b1.1.2" "b1.2" "b2"]))
|
||||
(is (= (map #(get % "title") result) ["b1" "b1.1" "b1.1.1" "b1.1.2" "b1.2" "b2"]))))
|
||||
(testing "insert batch blocks with properties"
|
||||
(let [page "insert batch blocks with properties"
|
||||
_ (page/new-page page)
|
||||
page-uuid (get (ls-api-call! :editor.getBlock page) "uuid")
|
||||
result (ls-api-call! :editor.insertBatchBlock page-uuid
|
||||
[{:content "b1"
|
||||
:children [{:content "b1.1"
|
||||
:children [{:content "b1.1.1"
|
||||
:properties {"z3" "Page 1"
|
||||
"z4" ["Page 2" "Page 3"]}}
|
||||
{:content "b1.1.2"}]}
|
||||
{:content "b1.2"}]
|
||||
:properties {"z1" "test"
|
||||
"z2" true}}
|
||||
{:content "b2"}]
|
||||
{:schema {"z3" "page"
|
||||
"z4" "page"}})
|
||||
contents (util/get-page-blocks-contents)]
|
||||
(is (= contents
|
||||
["b1" "test" "b1.1" "b1.1.1" "Page 1" "Page 2" "Page 3" "b1.1.2" "b1.2" "b2"]))
|
||||
(is (true? (get (first result) (->plugin-ident "z2")))))))
|
||||
|
||||
(deftest create-page-test
|
||||
(testing "create page"
|
||||
(let [result (ls-api-call! :editor.createPage "Test page 1")]
|
||||
(is (= "Test page 1" (get result "title")))
|
||||
(is
|
||||
(=
|
||||
":logseq.class/Page"
|
||||
(-> (ls-api-call! :editor.getBlock (first (get result "tags")))
|
||||
(get "ident"))))))
|
||||
|
||||
(testing "create page with properties"
|
||||
(let [result (ls-api-call! :editor.createPage "Test page 2"
|
||||
{:px1 "test"
|
||||
:px2 1
|
||||
:px3 "Page 1"
|
||||
:px4 ["Page 2" "Page 3"]}
|
||||
{:schema {:px3 {:type "page"}
|
||||
:px4 {:type "page"}}})
|
||||
page (ls-api-call! :editor.getBlock "Test page 2")]
|
||||
(is (= "Test page 2" (get result "title")))
|
||||
(is
|
||||
(=
|
||||
":logseq.class/Page"
|
||||
(-> (ls-api-call! :editor.getBlock (first (get result "tags")))
|
||||
(get "ident"))))
|
||||
;; verify properties
|
||||
(is (= "test" (-> (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px1") "id"]))
|
||||
(get "title"))))
|
||||
(is (= 1 (-> (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px2") "id"]))
|
||||
(get ":logseq.property/value"))))
|
||||
(let [page-1 (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px3") "id"]))]
|
||||
(is (= "page 1" (get page-1 "name"))))
|
||||
(is (= ["page 2" "page 3"] (map #(-> (ls-api-call! :editor.getBlock %)
|
||||
(get "name"))
|
||||
(map #(get % "id") (get page (->plugin-ident "px4"))))))))
|
||||
|
||||
(testing "create tag page"
|
||||
(let [result (ls-api-call! :editor.createPage "Tag new"
|
||||
{}
|
||||
{:class true})]
|
||||
(is
|
||||
(=
|
||||
":logseq.class/Tag"
|
||||
(-> (ls-api-call! :editor.getBlock (first (get result "tags")))
|
||||
(get "ident")))))))
|
||||
|
||||
(deftest get-all-tags-test
|
||||
(testing "get_all_tags"
|
||||
(let [result (ls-api-call! :editor.get_all_tags)
|
||||
built-in-tags #{":logseq.class/Template"
|
||||
":logseq.class/Query"
|
||||
":logseq.class/Math-block"
|
||||
":logseq.class/Pdf-annotation"
|
||||
":logseq.class/Task"
|
||||
":logseq.class/Code-block"
|
||||
":logseq.class/Card"
|
||||
":logseq.class/Quote-block"
|
||||
":logseq.class/Cards"}]
|
||||
(is (set/subset? built-in-tags (set (map #(get % "ident") result)))))))
|
||||
|
||||
(deftest get-all-properties-test
|
||||
(testing "get_all_properties"
|
||||
(let [result (ls-api-call! :editor.get_all_properties)]
|
||||
(is (>= (count result) 94)))))
|
||||
|
||||
(deftest get-tag-objects-test
|
||||
(testing "get_tag_objects"
|
||||
(let [page "tag objects test"
|
||||
_ (page/new-page page)
|
||||
_ (ls-api-call! :editor.insertBlock page "task 1"
|
||||
{:properties {"logseq.property/status" "Doing"}})
|
||||
result (ls-api-call! :editor.get_tag_objects "logseq.class/Task")]
|
||||
(is (= (count result) 1))
|
||||
(is (= "task 1" (get (first result) "title"))))))
|
||||
|
||||
3
deps.edn
3
deps.edn
@@ -5,7 +5,8 @@
|
||||
:sha "5d672bf84ed944414b9f61eeb83808ead7be9127"}
|
||||
|
||||
datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
|
||||
:sha "45f6721bf2038c24eb9fe3afb422322ab3f473b5"}
|
||||
:sha "3971e2d43bd93d89f42191dc7b4b092989e0cc61"}
|
||||
;; datascript/datascript {:local/root "../../datascript"}
|
||||
|
||||
datascript-transit/datascript-transit {:mvn/version "0.3.0"}
|
||||
borkdude/rewrite-edn {:mvn/version "0.4.9"}
|
||||
|
||||
2
deps/cli/package.json
vendored
2
deps/cli/package.json
vendored
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28",
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29",
|
||||
"better-sqlite3": "~11.10.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"jszip": "3.8.0",
|
||||
|
||||
6
deps/cli/yarn.lock
vendored
6
deps/cli/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
2
deps/common/package.json
vendored
2
deps/common/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"
|
||||
|
||||
12
deps/common/src/logseq/common/util.cljs
vendored
12
deps/common/src/logseq/common/util.cljs
vendored
@@ -6,7 +6,6 @@
|
||||
[cljs.reader :as reader]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as walk]
|
||||
[goog.string :as gstring]
|
||||
[logseq.common.log :as log]))
|
||||
|
||||
@@ -24,17 +23,6 @@
|
||||
[s]
|
||||
(.normalize s "NFC"))
|
||||
|
||||
(defn remove-nils
|
||||
"remove pairs of key-value that has nil value from a (possibly nested) map or
|
||||
coll of maps."
|
||||
[nm]
|
||||
(walk/postwalk
|
||||
(fn [el]
|
||||
(if (map? el)
|
||||
(into {} (remove (comp nil? second)) el)
|
||||
el))
|
||||
nm))
|
||||
|
||||
(defn remove-nils-non-nested
|
||||
"remove pairs of key-value that has nil value from a map (nested not supported)."
|
||||
[nm]
|
||||
|
||||
6
deps/common/yarn.lock
vendored
6
deps/common/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
2
deps/db/.carve/ignore
vendored
2
deps/db/.carve/ignore
vendored
@@ -46,5 +46,7 @@ logseq.db.sqlite.gc/gc-kvs-table!
|
||||
logseq.db.sqlite.gc/gc-kvs-table-node-version!
|
||||
;; API
|
||||
logseq.db.sqlite.gc/ensure-no-garbage
|
||||
;; API
|
||||
logseq.db.common.entity-util/entity->map
|
||||
;; documenting keywords
|
||||
logseq.db.frontend.kv-entity/kv-entities
|
||||
|
||||
3
deps/db/deps.edn
vendored
3
deps/db/deps.edn
vendored
@@ -1,7 +1,8 @@
|
||||
{:deps
|
||||
;; These nbb-logseq deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
|
||||
{datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
|
||||
:sha "45f6721bf2038c24eb9fe3afb422322ab3f473b5"}
|
||||
:sha "3971e2d43bd93d89f42191dc7b4b092989e0cc61"}
|
||||
;; datascript/datascript {:local/root "../../../../datascript"}
|
||||
datascript-transit/datascript-transit {:mvn/version "0.3.0"
|
||||
:exclusions [datascript/datascript]}
|
||||
cljs-bean/cljs-bean {:mvn/version "1.5.0"}
|
||||
|
||||
2
deps/db/package.json
vendored
2
deps/db/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "11.10.0"
|
||||
|
||||
8
deps/db/script/create_graph.cljs
vendored
8
deps/db/script/create_graph.cljs
vendored
@@ -5,7 +5,7 @@
|
||||
["path" :as node-path]
|
||||
[babashka.cli :as cli]
|
||||
[clojure.edn :as edn]
|
||||
[datascript.core :as d]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.sqlite-cli :as sqlite-cli]
|
||||
[logseq.db.sqlite.export :as sqlite-export]
|
||||
[logseq.outliner.cli :as outliner-cli]
|
||||
@@ -51,9 +51,9 @@
|
||||
(count (filter :block/title init-tx)) "blocks ...")
|
||||
;; (fs/writeFileSync "txs.edn" (with-out-str (cljs.pprint/pprint _txs)))
|
||||
;; (cljs.pprint/pprint _txs)
|
||||
(d/transact! conn init-tx)
|
||||
(when (seq block-props-tx) (d/transact! conn block-props-tx))
|
||||
(when (seq misc-tx) (d/transact! conn misc-tx))
|
||||
(ldb/transact! conn init-tx)
|
||||
(when (seq block-props-tx) (ldb/transact! conn block-props-tx))
|
||||
(when (seq misc-tx) (ldb/transact! conn misc-tx))
|
||||
(println (if graph-exists? "Updated graph" "Created graph") (str db-name "!"))
|
||||
(when (:validate options)
|
||||
(validate-db/validate-db @conn db-name {:group-errors true :closed-maps true :humanize true}))))
|
||||
|
||||
133
deps/db/src/logseq/db.cljs
vendored
133
deps/db/src/logseq/db.cljs
vendored
@@ -5,6 +5,7 @@
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as walk]
|
||||
[datascript.conn :as dc]
|
||||
[datascript.core :as d]
|
||||
[datascript.impl.entity :as de]
|
||||
[logseq.common.config :as common-config]
|
||||
@@ -20,6 +21,7 @@
|
||||
[logseq.db.frontend.entity-util :as entity-util]
|
||||
[logseq.db.frontend.property :as db-property]
|
||||
[logseq.db.frontend.schema :as db-schema]
|
||||
[logseq.db.frontend.validate :as db-validate]
|
||||
[logseq.db.sqlite.util :as sqlite-util])
|
||||
(:refer-clojure :exclude [object?]))
|
||||
|
||||
@@ -32,9 +34,18 @@
|
||||
(def build-favorite-tx db-db/build-favorite-tx)
|
||||
|
||||
(defonce *transact-fn (atom nil))
|
||||
(defonce *transact-invalid-callback (atom nil))
|
||||
(defonce *transact-pipeline-fn (atom nil))
|
||||
|
||||
(defn register-transact-fn!
|
||||
[f]
|
||||
(when f (reset! *transact-fn f)))
|
||||
(defn register-transact-invalid-callback-fn!
|
||||
[f]
|
||||
(when f (reset! *transact-invalid-callback f)))
|
||||
(defn register-transact-pipeline-fn!
|
||||
[f]
|
||||
(when f (reset! *transact-pipeline-fn f)))
|
||||
|
||||
(defn- remove-temp-block-data
|
||||
[tx-data]
|
||||
@@ -59,35 +70,87 @@
|
||||
data))
|
||||
tx-data)))
|
||||
|
||||
(defn assert-no-entities
|
||||
(defn entity->db-id
|
||||
[tx-data]
|
||||
(walk/prewalk
|
||||
(fn [f]
|
||||
(if (de/entity? f)
|
||||
(throw (ex-info "ldb/transact! doesn't support Entity"
|
||||
{:entity f
|
||||
:tx-data tx-data}))
|
||||
(if-let [id (:db/id f)]
|
||||
id
|
||||
(throw (ex-info "ldb/transact! doesn't support Entity"
|
||||
{:entity f
|
||||
:tx-data tx-data})))
|
||||
f))
|
||||
tx-data))
|
||||
|
||||
(comment
|
||||
(defn- skip-db-validate?
|
||||
[datoms]
|
||||
(every?
|
||||
(fn [d]
|
||||
(contains? #{:logseq.property/created-by-ref :block/refs :block/tx-id}
|
||||
(:a d)))
|
||||
datoms)))
|
||||
|
||||
(defn- throw-if-page-has-block-parent!
|
||||
[db tx-data]
|
||||
(when (some (fn [d] (and (:added d)
|
||||
(= :block/parent (:a d))
|
||||
(entity-util/page? (d/entity db (:e d)))
|
||||
(not (entity-util/page? (d/entity db (:v d)))))) tx-data)
|
||||
(throw (ex-info "Page can't have block as parent"
|
||||
{:tx-data tx-data}))))
|
||||
|
||||
(defn- transact-sync
|
||||
[repo-or-conn tx-data tx-meta]
|
||||
(try
|
||||
(let [conn repo-or-conn
|
||||
db @conn
|
||||
db-based? (entity-plus/db-based-graph? db)]
|
||||
(if (and db-based?
|
||||
(not (:reset-conn! tx-meta))
|
||||
(not (:initial-db? tx-meta))
|
||||
(not (:skip-validate-db? tx-meta false))
|
||||
(not (:logseq.graph-parser.exporter/new-graph? tx-meta)))
|
||||
(let [tx-report* (d/with db tx-data tx-meta)
|
||||
pipeline-f @*transact-pipeline-fn
|
||||
tx-report (if-let [f pipeline-f] (f tx-report*) tx-report*)
|
||||
_ (throw-if-page-has-block-parent! (:db-after tx-report) (:tx-data tx-report))
|
||||
validate-result (db-validate/validate-tx-report tx-report nil)]
|
||||
(if validate-result
|
||||
(when (and tx-report (seq (:tx-data tx-report)))
|
||||
;; perf enhancement: avoid repeated call on `d/with`
|
||||
(reset! conn (:db-after tx-report))
|
||||
(dc/store-after-transact! conn tx-report)
|
||||
(dc/run-callbacks conn tx-report))
|
||||
(do
|
||||
;; notify ui
|
||||
(when-let [f @*transact-invalid-callback]
|
||||
(f tx-report))
|
||||
(throw (ex-info "DB write failed with invalid data" {:tx-data tx-data}))))
|
||||
tx-report)
|
||||
(d/transact! conn tx-data tx-meta)))
|
||||
(catch :default e
|
||||
(prn :debug :transact-failed :tx-meta tx-meta :tx-data tx-data)
|
||||
(throw e))))
|
||||
|
||||
(defn transact!
|
||||
"`repo-or-conn`: repo for UI thread and conn for worker/node"
|
||||
([repo-or-conn tx-data]
|
||||
(transact! repo-or-conn tx-data nil))
|
||||
([repo-or-conn tx-data tx-meta]
|
||||
(when (or (exists? js/process)
|
||||
(and (exists? js/goog) js/goog.DEBUG))
|
||||
(assert-no-entities tx-data))
|
||||
(let [tx-data (map (fn [m]
|
||||
(if (map? m)
|
||||
(cond->
|
||||
(dissoc m :block/children :block/meta :block/top? :block/bottom? :block/anchor
|
||||
:block/level :block/container :db/other-tx
|
||||
:block/unordered)
|
||||
(not @*transact-fn)
|
||||
(dissoc :block.temp/load-status))
|
||||
m)) tx-data)
|
||||
tx-data (->> (remove-temp-block-data tx-data)
|
||||
(let [tx-data (->> tx-data
|
||||
entity->db-id
|
||||
(map (fn [m]
|
||||
(if (map? m)
|
||||
(cond->
|
||||
(dissoc m :block/children :block/meta :block/top? :block/bottom? :block/anchor
|
||||
:block/level :block/container :db/other-tx
|
||||
:block/unordered)
|
||||
(not @*transact-fn)
|
||||
(dissoc :block.temp/load-status))
|
||||
m)))
|
||||
(remove-temp-block-data)
|
||||
(common-util/fast-remove-nils)
|
||||
(remove empty?))
|
||||
delete-blocks-tx (when-not (string? repo-or-conn)
|
||||
@@ -96,7 +159,7 @@
|
||||
|
||||
;; Ensure worker can handle the request sequentially (one by one)
|
||||
;; Because UI assumes that the in-memory db has all the data except the last one transaction
|
||||
(when (or (seq tx-data) (:db-persist? tx-meta))
|
||||
(when (seq tx-data)
|
||||
|
||||
;; (prn :debug :transact :sync? (= d/transact! (or @*transact-fn d/transact!)) :tx-meta tx-meta)
|
||||
;; (cljs.pprint/pprint tx-data)
|
||||
@@ -104,12 +167,7 @@
|
||||
|
||||
(if-let [transact-fn @*transact-fn]
|
||||
(transact-fn repo-or-conn tx-data tx-meta)
|
||||
(try
|
||||
(d/transact! repo-or-conn tx-data tx-meta)
|
||||
(catch :default e
|
||||
(js/console.trace)
|
||||
(prn :debug :transact-failed :tx-meta tx-meta :tx-data tx-data)
|
||||
(throw e))))))))
|
||||
(transact-sync repo-or-conn tx-data tx-meta))))))
|
||||
|
||||
(def page? common-entity-util/page?)
|
||||
(def internal-page? entity-util/internal-page?)
|
||||
@@ -167,7 +225,7 @@
|
||||
closed-property (:block/closed-value-property block)]
|
||||
(sort-by-order (cond
|
||||
closed-property
|
||||
(:property/closed-values closed-property)
|
||||
(:block/_closed-value-property closed-property)
|
||||
|
||||
from-property
|
||||
(filter (fn [e]
|
||||
@@ -467,31 +525,6 @@
|
||||
(set)
|
||||
(set/union #{page-id})))
|
||||
|
||||
(defn get-block-refs
|
||||
[db id]
|
||||
(let [entity (d/entity db id)
|
||||
db-based? (db-based-graph? db)
|
||||
alias (->> (get-block-alias db id)
|
||||
(cons id)
|
||||
distinct)
|
||||
ref-ids (->> (mapcat (fn [id]
|
||||
(cond->> (:block/_refs (d/entity db id))
|
||||
db-based?
|
||||
(remove (fn [ref]
|
||||
;; remove refs that have the block as either tag or property
|
||||
(or (and
|
||||
(class? entity)
|
||||
(d/datom db :eavt (:db/id ref) :block/tags (:db/id entity)))
|
||||
(and
|
||||
(property? entity)
|
||||
(d/datom db :eavt (:db/id ref) (:db/ident entity))))))
|
||||
true
|
||||
(map :db/id)))
|
||||
alias)
|
||||
distinct)]
|
||||
(when (seq ref-ids)
|
||||
(d/pull-many db '[*] ref-ids))))
|
||||
|
||||
(def get-block-refs-count common-initial-data/get-block-refs-count)
|
||||
|
||||
(defn hidden-or-internal-tag?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns logseq.db.common.entity-util
|
||||
"Lower level entity util fns for DB and file graphs"
|
||||
(:require [logseq.db.file-based.entity-util :as file-entity-util]
|
||||
(:require [datascript.impl.entity :as de]
|
||||
[logseq.db.file-based.entity-util :as file-entity-util]
|
||||
[logseq.db.frontend.entity-util :as entity-util]))
|
||||
|
||||
(defn whiteboard?
|
||||
@@ -17,3 +18,9 @@
|
||||
[entity]
|
||||
(or (entity-util/page? entity)
|
||||
(file-entity-util/page? entity)))
|
||||
|
||||
(defn entity->map
|
||||
"Convert a db Entity to a map"
|
||||
[e]
|
||||
(assert (de/entity? e))
|
||||
(assoc (into {} e) :db/id (:db/id e)))
|
||||
|
||||
6
deps/db/src/logseq/db/common/view.cljs
vendored
6
deps/db/src/logseq/db/common/view.cljs
vendored
@@ -318,11 +318,7 @@
|
||||
(get-entities-for-all-pages db sorting property-ident {:db-based? db-based?})
|
||||
|
||||
:class-objects
|
||||
(let [class-id view-for-id
|
||||
class-children (db-class/get-structured-children db class-id)
|
||||
class-ids (distinct (conj class-children class-id))
|
||||
datoms (mapcat (fn [id] (d/datoms db :avet :block/tags id)) class-ids)]
|
||||
(keep (fn [d] (non-hidden-e (:e d))) datoms))
|
||||
(db-class/get-class-objects db view-for-id)
|
||||
|
||||
:property-objects
|
||||
(->>
|
||||
|
||||
12
deps/db/src/logseq/db/frontend/class.cljs
vendored
12
deps/db/src/logseq/db/frontend/class.cljs
vendored
@@ -7,6 +7,7 @@
|
||||
[flatland.ordered.map :refer [ordered-map]]
|
||||
[logseq.common.defkeywords :refer [defkeywords]]
|
||||
[logseq.db.frontend.db-ident :as db-ident]
|
||||
[logseq.db.frontend.entity-util :as entity-util]
|
||||
[logseq.db.frontend.rules :as rules]
|
||||
[logseq.db.sqlite.util :as sqlite-util]))
|
||||
|
||||
@@ -173,3 +174,14 @@
|
||||
"Determines if namespace string is a user class"
|
||||
[s]
|
||||
(string/includes? s ".class"))
|
||||
|
||||
(defn get-class-objects
|
||||
"Get class objects including children classes'"
|
||||
[db class-id]
|
||||
(let [class-children (get-structured-children db class-id)
|
||||
class-ids (distinct (conj class-children class-id))
|
||||
datoms (mapcat (fn [id] (d/datoms db :avet :block/tags id)) class-ids)
|
||||
non-hidden-e (fn [id] (let [e (d/entity db id)]
|
||||
(when-not (entity-util/hidden? e)
|
||||
e)))]
|
||||
(keep (fn [d] (non-hidden-e (:e d))) datoms)))
|
||||
|
||||
48
deps/db/src/logseq/db/frontend/db_ident.cljc
vendored
48
deps/db/src/logseq/db/frontend/db_ident.cljc
vendored
@@ -56,6 +56,13 @@
|
||||
(str id)))
|
||||
id)))))
|
||||
|
||||
(defn normalize-ident-name-part
|
||||
[name-string]
|
||||
(->> (string/replace-first name-string #"^(\d)" "NUM-$1")
|
||||
;; '-' must go last in char class
|
||||
(filter #(re-find #"[0-9a-zA-Z*+!_'?<>=-]{1}" %))
|
||||
(apply str)))
|
||||
|
||||
(defn create-db-ident-from-name
|
||||
"Creates a :db/ident for a class or property by sanitizing the given name.
|
||||
The created ident must obey clojure's rules for keywords i.e.
|
||||
@@ -63,32 +70,23 @@
|
||||
|
||||
NOTE: Only use this when creating a db-ident for a new class/property. Using
|
||||
this in read-only contexts like querying can result in db-ident conflicts"
|
||||
([user-namespace name-string]
|
||||
(create-db-ident-from-name user-namespace name-string true))
|
||||
([user-namespace name-string random-suffix?]
|
||||
{:pre [(or (keyword? user-namespace) (string? user-namespace)) (string? name-string) (boolean? random-suffix?)]}
|
||||
(assert (not (re-find #"^(logseq|block)(\.|$)" (name user-namespace)))
|
||||
"New ident is not allowed to use an internal namespace")
|
||||
(if #?(:org.babashka/nbb true
|
||||
:cljs (or (false? random-suffix?)
|
||||
(and (exists? js/process)
|
||||
(or js/process.env.REPEATABLE_IDENTS js/process.env.DB_GRAPH)))
|
||||
:default false)
|
||||
[user-namespace name-string]
|
||||
{:pre [(or (keyword? user-namespace) (string? user-namespace)) (string? name-string)]}
|
||||
(assert (not (re-find #"^(logseq|block)(\.|$)" (name user-namespace)))
|
||||
"New ident is not allowed to use an internal namespace")
|
||||
(if #?(:org.babashka/nbb true
|
||||
:cljs (and (exists? js/process)
|
||||
(or js/process.env.REPEATABLE_IDENTS js/process.env.DB_GRAPH))
|
||||
:default false)
|
||||
;; Used for contexts where we want repeatable idents e.g. tests and CLIs
|
||||
(keyword user-namespace
|
||||
(->> (string/replace-first name-string #"^(\d)" "NUM-$1")
|
||||
;; '-' must go last in char class
|
||||
(filter #(re-find #"[0-9a-zA-Z*+!_'?<>=-]{1}" %))
|
||||
(apply str)))
|
||||
(keyword user-namespace
|
||||
(str
|
||||
(->> (string/replace-first name-string #"^(\d)" "NUM-$1")
|
||||
;; '-' must go last in char class
|
||||
(filter #(re-find #"[0-9a-zA-Z*+!_'?<>=-]{1}" %))
|
||||
(apply str))
|
||||
"-"
|
||||
(rand-nth non-int-char-range)
|
||||
(nano-id 7))))))
|
||||
(keyword user-namespace (normalize-ident-name-part name-string))
|
||||
(let [suffix (str "-"
|
||||
(rand-nth non-int-char-range)
|
||||
(nano-id 7))]
|
||||
(keyword user-namespace
|
||||
(str
|
||||
(normalize-ident-name-part name-string)
|
||||
suffix)))))
|
||||
|
||||
(defn replace-db-ident-random-suffix
|
||||
[db-ident-kw new-suffix]
|
||||
|
||||
@@ -102,7 +102,11 @@
|
||||
(seq (:property/closed-values property)))
|
||||
(fn closed-value-valid? [val]
|
||||
(and (validate-fn' val)
|
||||
(contains? (set (map :db/id (:property/closed-values property))) val)))
|
||||
(let [ids (set (map :db/id (:property/closed-values property)))
|
||||
result (contains? ids val)]
|
||||
(when-not result
|
||||
(js/console.error (str "Error: not a closed value, id: " val ", existing choices: " ids ", property: " (:db/ident property))))
|
||||
result)))
|
||||
validate-fn')]
|
||||
(if (db-property/many? property)
|
||||
(or (every? validate-fn'' property-val)
|
||||
@@ -346,7 +350,7 @@
|
||||
(concat
|
||||
[:map
|
||||
[:db/ident plugin-property-ident]
|
||||
[:logseq.property/type (apply vector :enum (conj db-property-type/user-built-in-property-types :string))]]
|
||||
[:logseq.property/type (apply vector :enum (concat db-property-type/user-built-in-property-types [:json :string :page]))]]
|
||||
property-common-schema-attrs
|
||||
property-attrs
|
||||
page-attrs
|
||||
|
||||
3
deps/db/src/logseq/db/frontend/property.cljs
vendored
3
deps/db/src/logseq/db/frontend/property.cljs
vendored
@@ -683,7 +683,8 @@
|
||||
[db property-id]
|
||||
(when db
|
||||
(when-let [property (d/entity db property-id)]
|
||||
(:property/closed-values property))))
|
||||
(some->> (:block/_closed-value-property property)
|
||||
(sort-by :block/order)))))
|
||||
|
||||
(defn closed-value-content
|
||||
"Gets content/value of a given closed value ent/map. Works for all closed value types"
|
||||
|
||||
@@ -111,7 +111,10 @@
|
||||
(assert (:db/ident property-map) "Key in map must have a :db/ident")
|
||||
(when pure? (assert (some? gen-uuid-value-prefix) block))
|
||||
[(or (:original-property-id property-map) (:db/ident property-map))
|
||||
(if (set? v)
|
||||
(cond
|
||||
(and (set? v) (every? uuid? v))
|
||||
(set (map #(vector :block/uuid %) v))
|
||||
(set? v)
|
||||
(set (map #(build-property-value-block
|
||||
block' property-map %
|
||||
(cond-> {}
|
||||
@@ -121,6 +124,9 @@
|
||||
(assoc :block-uuid
|
||||
(common-uuid/gen-uuid :builtin-block-uuid (str gen-uuid-value-prefix "-" %)))))
|
||||
v))
|
||||
(uuid? v)
|
||||
[:block/uuid v]
|
||||
:else
|
||||
(build-property-value-block block' property-map v
|
||||
(cond-> {}
|
||||
property-value-properties
|
||||
@@ -130,12 +136,25 @@
|
||||
(common-uuid/gen-uuid :builtin-block-uuid (str gen-uuid-value-prefix "-" v))))))])))
|
||||
(into {}))))
|
||||
|
||||
(defn- lookup-id?
|
||||
[v]
|
||||
(and (vector? v)
|
||||
(= 2 (count v))
|
||||
(= :block/uuid (first v))
|
||||
(uuid? (second v))))
|
||||
|
||||
(defn build-properties-with-ref-values
|
||||
"Given a properties map with property values to be transacted e.g. from
|
||||
build-property-values-tx-m, build a properties map to be transacted with the block"
|
||||
[prop-vals-tx-m]
|
||||
(update-vals prop-vals-tx-m
|
||||
(fn [v]
|
||||
(if (set? v)
|
||||
(cond
|
||||
(and (set? v) (every? lookup-id? v))
|
||||
v
|
||||
(set? v)
|
||||
(set (map #(vector :block/uuid (:block/uuid %)) v))
|
||||
(lookup-id? v)
|
||||
v
|
||||
:else
|
||||
(vector :block/uuid (:block/uuid v))))))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
(def internal-built-in-property-types
|
||||
"Valid property types only for use by internal built-in-properties"
|
||||
#{:keyword :map :coll :any :entity :class :page :property :string :raw-number})
|
||||
#{:keyword :map :coll :any :entity :class :page :property :string :json :raw-number})
|
||||
|
||||
(def user-built-in-property-types
|
||||
"Valid property types for users in order they appear in the UI"
|
||||
@@ -134,7 +134,8 @@
|
||||
(if new-closed-value?
|
||||
(string? s)
|
||||
(when-let [ent (d/entity db s)]
|
||||
(string? (:block/title ent)))))
|
||||
(and (string? (:block/title ent))
|
||||
(some? (:block/page ent))))))
|
||||
|
||||
(defn- node-entity?
|
||||
[db val]
|
||||
@@ -147,33 +148,17 @@
|
||||
(and (some? (:block/title ent))
|
||||
(entity-util/journal? ent))))
|
||||
|
||||
(def built-in-validation-schemas
|
||||
"Map of types to malli validation schemas that validate a property value for that type"
|
||||
{:default [:fn
|
||||
{:error/message "should be a text block"}
|
||||
text-entity?]
|
||||
:number [:fn
|
||||
{:error/message "should be a number"}
|
||||
number-entity?]
|
||||
:date [:fn
|
||||
{:error/message "should be a journal date"}
|
||||
date?]
|
||||
:datetime [:fn
|
||||
{:error/message "should be a datetime"}
|
||||
number?]
|
||||
:checkbox boolean?
|
||||
:url [:fn
|
||||
{:error/message "should be a URL"}
|
||||
url-entity?]
|
||||
:node [:fn
|
||||
{:error/message "should be a page/block with tags"}
|
||||
node-entity?]
|
||||
|
||||
;; Internal usage
|
||||
;; ==============
|
||||
|
||||
:string string?
|
||||
:raw-number number?
|
||||
;; Internal usage
|
||||
(def internal-validation-schemas
|
||||
{:string [:fn
|
||||
{:error/message "should be a string"}
|
||||
string?]
|
||||
:json [:fn
|
||||
{:error/message "should be JSON string"}
|
||||
string?]
|
||||
:raw-number [:fn
|
||||
{:error/message "should be a raw number"}
|
||||
number?]
|
||||
:entity [:fn
|
||||
{:error/message "should be an Entity"}
|
||||
entity?]
|
||||
@@ -186,12 +171,44 @@
|
||||
:page [:fn
|
||||
{:error/message "should be a Page"}
|
||||
page-entity?]
|
||||
:keyword keyword?
|
||||
:map map?
|
||||
:keyword [:fn
|
||||
{:error/message "should be a Clojure keyword"}
|
||||
keyword?]
|
||||
:map [:fn
|
||||
{:error/message "should be a Clojure map"}
|
||||
map?]
|
||||
;; coll elements are ordered as it's saved as a vec
|
||||
:coll coll?
|
||||
:coll [:fn
|
||||
{:error/message "should be a collection"}
|
||||
coll?]
|
||||
:any some?})
|
||||
|
||||
(def built-in-validation-schemas
|
||||
"Map of types to malli validation schemas that validate a property value for that type"
|
||||
(into
|
||||
{:default [:fn
|
||||
{:error/message "should be a text block"}
|
||||
text-entity?]
|
||||
:number [:fn
|
||||
{:error/message "should be a number"}
|
||||
number-entity?]
|
||||
:date [:fn
|
||||
{:error/message "should be a journal date"}
|
||||
date?]
|
||||
:datetime [:fn
|
||||
{:error/message "should be a datetime"}
|
||||
number?]
|
||||
:checkbox [:fn
|
||||
{:error/message "should be a boolean"}
|
||||
boolean?]
|
||||
:url [:fn
|
||||
{:error/message "should be a URL"}
|
||||
url-entity?]
|
||||
:node [:fn
|
||||
{:error/message "should be a page/block with tags"}
|
||||
node-entity?]}
|
||||
internal-validation-schemas))
|
||||
|
||||
(assert (= (set (keys built-in-validation-schemas))
|
||||
(into internal-built-in-property-types
|
||||
user-built-in-property-types))
|
||||
|
||||
6
deps/db/src/logseq/db/frontend/validate.cljs
vendored
6
deps/db/src/logseq/db/frontend/validate.cljs
vendored
@@ -21,10 +21,10 @@
|
||||
[closed-schema?]
|
||||
(if closed-schema? closed-db-schema-explainer db-schema-explainer))
|
||||
|
||||
(defn validate-tx-report!
|
||||
(defn validate-tx-report
|
||||
"Validates the datascript tx-report for entities that have changed. Returns
|
||||
boolean indicating if db is valid"
|
||||
[{:keys [db-after tx-data tx-meta]} validate-options]
|
||||
[{:keys [db-after tx-data _tx-meta]} validate-options]
|
||||
(let [changed-ids (->> tx-data (keep :e) distinct)
|
||||
tx-datoms (mapcat #(d/datoms db-after :eavt %) changed-ids)
|
||||
ent-maps* (map (fn [[db-id m]]
|
||||
@@ -38,7 +38,7 @@
|
||||
;; remove :db/id as it adds needless declarations to schema
|
||||
#(validator [(dissoc % :db/id)])
|
||||
ent-maps)]
|
||||
(prn "changed eids:" changed-ids :tx-meta tx-meta)
|
||||
;; (prn "changed eids:" changed-ids :tx-meta tx-meta)
|
||||
(if (seq invalid-ent-maps)
|
||||
(let [explainer (get-schema-explainer (:closed-schema? validate-options))]
|
||||
(prn "Invalid datascript entities detected amongst changed entity ids:" changed-ids)
|
||||
|
||||
21
deps/db/src/logseq/db/sqlite/build.cljs
vendored
21
deps/db/src/logseq/db/sqlite/build.cljs
vendored
@@ -140,9 +140,20 @@
|
||||
(cond-> property-map
|
||||
(and (:build/property-value v) (seq pvalue-attrs))
|
||||
(assoc :property-value-properties pvalue-attrs)))
|
||||
(if (:build/property-value v)
|
||||
(or (:logseq.property/value v) (:block/title v))
|
||||
v)])))
|
||||
(let [property (when (keyword? k) (get properties-config k))
|
||||
closed-value-id (when property (some (fn [item]
|
||||
(when (= (:value item) v)
|
||||
(:uuid item)))
|
||||
(get property :build/closed-values)))]
|
||||
(cond
|
||||
closed-value-id
|
||||
closed-value-id
|
||||
|
||||
(:build/property-value v)
|
||||
(or (:logseq.property/value v) (:block/title v))
|
||||
|
||||
:else
|
||||
v))])))
|
||||
(db-property-build/build-property-values-tx-m new-block)))
|
||||
|
||||
(defn- extract-basic-content-refs
|
||||
@@ -502,7 +513,9 @@
|
||||
[init-tx block-props-tx]
|
||||
(reduce (fn [[init-tx* block-props-tx*] m]
|
||||
(let [props (select-keys m property-idents)]
|
||||
[(conj init-tx* (apply dissoc m property-idents))
|
||||
[(if (map? m)
|
||||
(conj init-tx* (apply dissoc m property-idents))
|
||||
init-tx*)
|
||||
(if (seq props)
|
||||
(conj block-props-tx*
|
||||
(merge {:block/uuid (or (:block/uuid m)
|
||||
|
||||
6
deps/db/yarn.lock
vendored
6
deps/db/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
2
deps/graph-parser/package.json
vendored
2
deps/graph-parser/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28",
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29",
|
||||
"better-sqlite3": "11.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
6
deps/graph-parser/script/db_import.cljs
vendored
6
deps/graph-parser/script/db_import.cljs
vendored
@@ -12,6 +12,7 @@
|
||||
[datascript.core :as d]
|
||||
[logseq.common.config :as common-config]
|
||||
[logseq.common.graph :as common-graph]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.sqlite-cli :as sqlite-cli]
|
||||
[logseq.db.frontend.asset :as db-asset]
|
||||
[logseq.graph-parser.exporter :as gp-exporter]
|
||||
@@ -22,7 +23,7 @@
|
||||
[promesa.core :as p]))
|
||||
|
||||
(def tx-queue (atom cljs.core/PersistentQueue.EMPTY))
|
||||
(def original-transact! d/transact!)
|
||||
(def original-transact! ldb/transact!)
|
||||
(defn dev-transact! [conn tx-data tx-meta]
|
||||
(swap! tx-queue (fn [queue]
|
||||
(let [new-queue (conj queue {:tx-data tx-data :tx-meta tx-meta})]
|
||||
@@ -94,8 +95,7 @@
|
||||
(println (some-> (get-in m [:ex-data :error]) .-stack)))
|
||||
(when debug
|
||||
(when-let [matching-tx (seq (filter #(and (get-in m [:ex-data :path])
|
||||
(or (= (get-in % [:tx-meta ::gp-exporter/path]) (get-in m [:ex-data :path]))
|
||||
(= (get-in % [:tx-meta ::outliner-pipeline/original-tx-meta ::gp-exporter/path]) (get-in m [:ex-data :path]))))
|
||||
(= (get-in % [:tx-meta ::gp-exporter/path]) (get-in m [:ex-data :path])))
|
||||
@tx-queue))]
|
||||
(println (str "\n" (count matching-tx)) "Tx Maps for failing path:")
|
||||
(pprint/pprint matching-tx))))
|
||||
|
||||
@@ -1791,7 +1791,7 @@
|
||||
(split-pages-and-properties-tx pages-tx old-properties existing-pages (:import-state options) @(:upstream-properties tx-options))
|
||||
;; _ (when (seq property-pages-tx) (cljs.pprint/pprint {:property-pages-tx property-pages-tx}))
|
||||
;; Necessary to transact new property entities first so that block+page properties can be transacted next
|
||||
main-props-tx-report (d/transact! conn property-pages-tx {::new-graph? true ::path file})
|
||||
main-props-tx-report (ldb/transact! conn property-pages-tx {::new-graph? true ::path file})
|
||||
_ (save-from-tx property-pages-tx options)
|
||||
|
||||
classes-tx @(:classes-tx tx-options)
|
||||
@@ -1817,13 +1817,13 @@
|
||||
;; [:whiteboard-pages :pages-index :page-properties-tx :property-page-properties-tx :pages-tx' :classes-tx :blocks-index :blocks-tx]
|
||||
;; [whiteboard-pages pages-index page-properties-tx property-page-properties-tx pages-tx' classes-tx blocks-index blocks-tx]))
|
||||
;; _ (when (not (seq whiteboard-pages)) (cljs.pprint/pprint {#_:property-pages-tx #_property-pages-tx :pages-tx pages-tx :tx tx'}))
|
||||
main-tx-report (d/transact! conn tx' {::new-graph? true ::path file})
|
||||
main-tx-report (ldb/transact! conn tx' {::new-graph? true ::path file})
|
||||
_ (save-from-tx tx' options)
|
||||
|
||||
upstream-properties-tx
|
||||
(build-upstream-properties-tx @conn @(:upstream-properties tx-options) (:import-state options) log-fn)
|
||||
;; _ (when (seq upstream-properties-tx) (cljs.pprint/pprint {:upstream-properties-tx upstream-properties-tx}))
|
||||
upstream-tx-report (when (seq upstream-properties-tx) (d/transact! conn upstream-properties-tx {::new-graph? true ::path file}))
|
||||
upstream-tx-report (when (seq upstream-properties-tx) (ldb/transact! conn upstream-properties-tx {::new-graph? true ::path file}))
|
||||
_ (save-from-tx upstream-properties-tx options)]
|
||||
|
||||
;; Return all tx-reports that occurred in this fn as UI needs to know what changed
|
||||
|
||||
6
deps/graph-parser/yarn.lock
vendored
6
deps/graph-parser/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
4
deps/outliner/.carve/ignore
vendored
4
deps/outliner/.carve/ignore
vendored
@@ -9,4 +9,6 @@ logseq.outliner.op/register-op-handlers!
|
||||
;; API fn
|
||||
logseq.outliner.page/delete!
|
||||
;; API fn
|
||||
logseq.outliner.page/create!
|
||||
logseq.outliner.page/create!
|
||||
;; API fn
|
||||
logseq.outliner.property/validate!
|
||||
|
||||
3
deps/outliner/deps.edn
vendored
3
deps/outliner/deps.edn
vendored
@@ -1,7 +1,8 @@
|
||||
{:deps
|
||||
;; These nbb-logseq deps are kept in sync with https://github.com/logseq/nbb-logseq/blob/main/bb.edn
|
||||
{datascript/datascript {:git/url "https://github.com/logseq/datascript" ;; fork
|
||||
:sha "45f6721bf2038c24eb9fe3afb422322ab3f473b5"}
|
||||
:sha "3971e2d43bd93d89f42191dc7b4b092989e0cc61"}
|
||||
;; datascript/datascript {:local/root "../../../../datascript"}
|
||||
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
|
||||
|
||||
;; Any other deps should be added here and to nbb.edn
|
||||
|
||||
2
deps/outliner/package.json
vendored
2
deps/outliner/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "11.10.0",
|
||||
|
||||
3
deps/outliner/script/transact.cljs
vendored
3
deps/outliner/script/transact.cljs
vendored
@@ -2,6 +2,7 @@
|
||||
"This script generically runs transactions against the queried blocks"
|
||||
(:require [clojure.edn :as edn]
|
||||
[datascript.core :as d]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.sqlite-cli :as sqlite-cli]
|
||||
[logseq.db.frontend.rules :as rules]
|
||||
[logseq.outliner.db-pipeline :as db-pipeline]
|
||||
@@ -30,7 +31,7 @@
|
||||
(prn (map #(select-keys (d/entity @conn %) [:block/name :block/title]) blocks-to-update)))
|
||||
(do
|
||||
(db-pipeline/add-listener conn)
|
||||
(d/transact! conn update-tx)
|
||||
(ldb/transact! conn update-tx)
|
||||
(println "Updated" (count update-tx) "block(s) for graph" (str db-name "!"))))))
|
||||
|
||||
(when (= nbb/*file* (nbb/invoked-file))
|
||||
|
||||
8
deps/outliner/src/logseq/outliner/cli.cljs
vendored
8
deps/outliner/src/logseq/outliner/cli.cljs
vendored
@@ -5,8 +5,8 @@
|
||||
["path" :as node-path]
|
||||
[borkdude.rewrite-edn :as rewrite]
|
||||
[clojure.string :as string]
|
||||
[datascript.core :as d]
|
||||
[logseq.common.config :as common-config]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.sqlite-cli :as sqlite-cli]
|
||||
[logseq.db.sqlite.build :as sqlite-build]
|
||||
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
|
||||
@@ -48,9 +48,9 @@
|
||||
additional-config
|
||||
(pretty-print-merge additional-config))
|
||||
git-sha (get-git-sha)]
|
||||
(d/transact! conn (sqlite-create-graph/build-db-initial-data config-content
|
||||
(merge {:import-type import-type}
|
||||
(when git-sha {:graph-git-sha git-sha}))))))
|
||||
(ldb/transact! conn (sqlite-create-graph/build-db-initial-data config-content
|
||||
(merge {:import-type import-type}
|
||||
(when git-sha {:graph-git-sha git-sha}))))))
|
||||
|
||||
(defn init-conn
|
||||
"Create sqlite DB, initialize datascript connection and sync listener and then
|
||||
|
||||
25
deps/outliner/src/logseq/outliner/core.cljs
vendored
25
deps/outliner/src/logseq/outliner/core.cljs
vendored
@@ -288,8 +288,6 @@
|
||||
(dissoc :block/children :block/meta :block/unordered
|
||||
:block.temp/ast-title :block.temp/ast-body :block/level :block.temp/load-status
|
||||
:block.temp/has-children?)
|
||||
common-util/remove-nils
|
||||
|
||||
(fix-tag-ids db {:db-graph? db-based?}))
|
||||
(not collapse-or-expand?)
|
||||
block-with-updated-at)
|
||||
@@ -1103,12 +1101,13 @@
|
||||
;;; ### write-operations have side-effects (do transactions) ;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- op-transact!
|
||||
[f & args]
|
||||
[outliner-op f & args]
|
||||
{:pre [(fn? f)]}
|
||||
(try
|
||||
(let [result (apply f args)]
|
||||
(when result
|
||||
(let [tx-meta (assoc (:tx-meta result) :skip-store? true)]
|
||||
(let [tx-meta (assoc (:tx-meta result)
|
||||
:outliner-op outliner-op)]
|
||||
(ldb/transact! (second args) (:tx-data result) tx-meta)))
|
||||
result)
|
||||
(catch :default e
|
||||
@@ -1119,31 +1118,29 @@
|
||||
(save-block repo @conn date-formatter block opts))]
|
||||
(defn save-block!
|
||||
[repo conn date-formatter block & {:as opts}]
|
||||
(op-transact! f repo conn date-formatter block opts)))
|
||||
(op-transact! :save-block f repo conn date-formatter block opts)))
|
||||
|
||||
(let [f (fn [repo conn blocks target-block opts]
|
||||
(insert-blocks repo @conn blocks target-block opts))]
|
||||
(defn insert-blocks!
|
||||
[repo conn blocks target-block opts]
|
||||
(op-transact! f repo conn blocks target-block (assoc opts :outliner-op :insert-blocks))))
|
||||
(op-transact! :insert-blocks f repo conn blocks target-block (assoc opts :outliner-op :insert-blocks))))
|
||||
|
||||
(let [f (fn [_repo conn blocks opts]
|
||||
(let [{:keys [tx-data]} (delete-blocks @conn blocks)]
|
||||
{:tx-data tx-data
|
||||
:tx-meta (select-keys opts [:outliner-op])}))]
|
||||
(let [f (fn [_repo conn blocks _opts]
|
||||
(delete-blocks @conn blocks))]
|
||||
(defn delete-blocks!
|
||||
[repo conn _date-formatter blocks opts]
|
||||
(op-transact! f repo conn blocks opts)))
|
||||
(op-transact! :delete-blocks f repo conn blocks opts)))
|
||||
|
||||
(defn move-blocks!
|
||||
[repo conn blocks target-block opts]
|
||||
(op-transact! move-blocks repo conn blocks target-block
|
||||
(op-transact! :move-blocks move-blocks repo conn blocks target-block
|
||||
(assoc opts :outliner-op :move-blocks)))
|
||||
|
||||
(defn move-blocks-up-down!
|
||||
[repo conn blocks up?]
|
||||
(op-transact! move-blocks-up-down repo conn blocks up?))
|
||||
(op-transact! :move-blocks-up-down move-blocks-up-down repo conn blocks up?))
|
||||
|
||||
(defn indent-outdent-blocks!
|
||||
[repo conn blocks indent? & {:as opts}]
|
||||
(op-transact! indent-outdent-blocks repo conn blocks indent? opts))
|
||||
(op-transact! :indent-outdent-blocks indent-outdent-blocks repo conn blocks indent? opts))
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"Modified copy of frontend.worker.pipeline/invoke-hooks that handles new DB graphs but
|
||||
doesn't handle updating DB graphs well yet e.g. doesn't handle :block/tx-id"
|
||||
[conn tx-report]
|
||||
(when (not (get-in tx-report [:tx-meta :pipeline-replace?]))
|
||||
;; TODO: Handle block edits with separate :block/refs rebuild as deleting property values is buggy
|
||||
(when-not (:pipeline-replace? (:tx-meta tx-report))
|
||||
(outliner-pipeline/transact-new-db-graph-refs conn tx-report)))
|
||||
|
||||
(defn ^:api add-listener
|
||||
|
||||
27
deps/outliner/src/logseq/outliner/page.cljs
vendored
27
deps/outliner/src/logseq/outliner/page.cljs
vendored
@@ -26,15 +26,16 @@
|
||||
id-ref->page #(db-content/content-id-ref->page % [page-entity])]
|
||||
(when (seq refs)
|
||||
(let [tx-data (mapcat (fn [{:block/keys [raw-title] :as ref}]
|
||||
;; block content
|
||||
(let [content' (id-ref->page raw-title)
|
||||
content-tx (when (not= raw-title content')
|
||||
{:db/id (:db/id ref)
|
||||
:block/title content'})
|
||||
tx content-tx]
|
||||
(concat
|
||||
[[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]]
|
||||
(when tx [tx])))) refs)]
|
||||
;; block content
|
||||
(when raw-title
|
||||
(let [content' (id-ref->page raw-title)
|
||||
content-tx (when (not= raw-title content')
|
||||
{:db/id (:db/id ref)
|
||||
:block/title content'})
|
||||
tx content-tx]
|
||||
(concat
|
||||
[[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]]
|
||||
(when tx [tx]))))) refs)]
|
||||
tx-data))))
|
||||
|
||||
(defn delete!
|
||||
@@ -228,7 +229,7 @@
|
||||
[page])
|
||||
(remove nil?))))
|
||||
|
||||
(defn- ^:large-vars/cleanup-todo create
|
||||
(defn ^:large-vars/cleanup-todo ^:api create
|
||||
"Pure function without side effects"
|
||||
[db title*
|
||||
{uuid' :uuid
|
||||
@@ -305,7 +306,7 @@
|
||||
|
||||
(defn create!
|
||||
[conn title opts]
|
||||
(let [{:keys [tx-meta tx-data title page-uuid]} (create @conn title opts)]
|
||||
(let [{:keys [tx-meta tx-data title' page-uuid]} (create @conn title opts)]
|
||||
(when (seq tx-data)
|
||||
(d/transact! conn tx-data tx-meta)
|
||||
[title page-uuid])))
|
||||
(ldb/transact! conn tx-data tx-meta)
|
||||
[title' page-uuid])))
|
||||
|
||||
@@ -146,6 +146,6 @@
|
||||
[conn tx-report]
|
||||
(let [{:keys [blocks]} (ds-report/get-blocks-and-pages tx-report)
|
||||
refs-tx-report (when-let [refs-tx (and (seq blocks) (rebuild-block-refs-tx tx-report blocks))]
|
||||
(ldb/transact! conn refs-tx {:pipeline-replace? true
|
||||
::original-tx-meta (:tx-meta tx-report)}))]
|
||||
(ldb/transact! conn refs-tx (-> (:tx-meta tx-report)
|
||||
(assoc :pipeline-replace? true))))]
|
||||
refs-tx-report))
|
||||
|
||||
202
deps/outliner/src/logseq/outliner/property.cljs
vendored
202
deps/outliner/src/logseq/outliner/property.cljs
vendored
@@ -18,7 +18,9 @@
|
||||
[logseq.db.frontend.schema :as db-schema]
|
||||
[logseq.db.sqlite.util :as sqlite-util]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.outliner.page :as outliner-page]
|
||||
[logseq.outliner.validate :as outliner-validate]
|
||||
[malli.core :as m]
|
||||
[malli.error :as me]
|
||||
[malli.util :as mu]))
|
||||
|
||||
@@ -28,6 +30,14 @@
|
||||
(throw (ex-info "Read-only property value shouldn't be edited"
|
||||
{:property property-ident}))))
|
||||
|
||||
(defn- db-ident->eid
|
||||
[db db-ident]
|
||||
(assert (qualified-keyword? db-ident))
|
||||
(let [id (:db/id (d/entity db db-ident))]
|
||||
(when-not id
|
||||
(throw (ex-info "Wrong property db/ident" {:db-ident db-ident})))
|
||||
id))
|
||||
|
||||
(defonce ^:private built-in-class-property->properties
|
||||
(->>
|
||||
(mapcat
|
||||
@@ -194,7 +204,7 @@
|
||||
(or (not= (:logseq.property/type schema) (:logseq.property/type property))
|
||||
(and (:db/cardinality schema) (not= (:db/cardinality schema) (keyword (name (:db/cardinality property)))))
|
||||
(and (= :default (:logseq.property/type schema)) (not= :db.type/ref (:db/valueType property)))
|
||||
(seq (:property/closed-values property))))
|
||||
(seq (entity-plus/lookup-kv-then-entity property :property/closed-values))))
|
||||
(concat (update-datascript-schema property schema)))
|
||||
tx-data (concat property-tx-data
|
||||
(when (seq properties)
|
||||
@@ -227,27 +237,48 @@
|
||||
schema (get-property-value-schema db property-type property)]
|
||||
(validate-property-value-aux schema value {:many? many?})))
|
||||
|
||||
(defn- validate!
|
||||
"Validates `data` against `schema`.
|
||||
Throws an ex-info with readable message if validation fails."
|
||||
[property schema value]
|
||||
(when-not (and
|
||||
(= :db.type/ref (:db/valueType property))
|
||||
(= value :logseq.property/empty-placeholder))
|
||||
(when-not (m/validate schema value)
|
||||
(let [errors (-> (m/explain schema value)
|
||||
(me/humanize))
|
||||
error-msg (str "\"" (:block/title property) "\"" " " (if (coll? errors) (first errors) errors))]
|
||||
(throw
|
||||
(ex-info "Schema validation failed"
|
||||
{:type :notification
|
||||
:payload {:message error-msg
|
||||
:type :warning}
|
||||
:property (:db/ident property)
|
||||
:value value
|
||||
:errors errors}))))))
|
||||
|
||||
(defn- throw-error-if-invalid-property-value
|
||||
[db property value]
|
||||
(let [property-type (:logseq.property/type property)
|
||||
many? (= :db.cardinality/many (:db/cardinality property))
|
||||
schema (get-property-value-schema db property-type property)
|
||||
value' (if (and many? (not (sequential? value)))
|
||||
#{value}
|
||||
value)]
|
||||
(validate! property schema value')))
|
||||
|
||||
(defn- ->eid
|
||||
[id]
|
||||
(if (uuid? id) [:block/uuid id] id))
|
||||
|
||||
(defn- raw-set-block-property!
|
||||
"Adds the raw property pair (value not modified) to the given block if the property value is valid"
|
||||
[conn block property property-type new-value]
|
||||
[conn block property new-value]
|
||||
(throw-error-if-read-only-property (:db/ident property))
|
||||
(let [k-name (:block/title property)
|
||||
property-id (:db/ident property)
|
||||
schema (get-property-value-schema @conn property-type property)]
|
||||
(if-let [msg (and
|
||||
(not= new-value :logseq.property/empty-placeholder)
|
||||
(validate-property-value-aux schema new-value {:many? (db-property/many? property)}))]
|
||||
(let [msg' (str "\"" k-name "\"" " " (if (coll? msg) (first msg) msg))]
|
||||
(throw (ex-info "Schema validation failed"
|
||||
{:type :notification
|
||||
:payload {:message msg'
|
||||
:type :warning}})))
|
||||
(let [tx-data (build-property-value-tx-data conn block property-id new-value)]
|
||||
(ldb/transact! conn tx-data {:outliner-op :save-block})))))
|
||||
(throw-error-if-invalid-property-value @conn property new-value)
|
||||
(let [property-id (:db/ident property)
|
||||
tx-data (build-property-value-tx-data conn block property-id new-value)]
|
||||
(ldb/transact! conn tx-data {:outliner-op :save-block})))
|
||||
|
||||
(defn create-property-text-block!
|
||||
"Creates a property value block for the given property and value. Adds it to
|
||||
@@ -258,6 +289,11 @@
|
||||
_ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
|
||||
value' (convert-property-input-string (:logseq.property/type block)
|
||||
property value)
|
||||
_ (when (and (not= (:logseq.property/type property) :number)
|
||||
(not (string? value')))
|
||||
(throw (ex-info "value should be a string" {:block-id block-id
|
||||
:property-id property-id
|
||||
:value value'})))
|
||||
new-value-block (cond-> (db-property-build/build-property-value-block (or block property) property value')
|
||||
new-block-id
|
||||
(assoc :block/uuid new-block-id))]
|
||||
@@ -265,7 +301,7 @@
|
||||
(let [property-id (:db/ident property)]
|
||||
(when (and property-id block)
|
||||
(when-let [block-id (:db/id (d/entity @conn [:block/uuid (:block/uuid new-value-block)]))]
|
||||
(raw-set-block-property! conn block property (:logseq.property/type property) block-id)))
|
||||
(raw-set-block-property! conn block property block-id)))
|
||||
(:block/uuid new-value-block))))
|
||||
|
||||
(defn- get-property-value-eid
|
||||
@@ -285,11 +321,14 @@
|
||||
"Find or create a property value. Only to be used with properties that have ref types"
|
||||
[conn property-id v]
|
||||
(let [property (d/entity @conn property-id)
|
||||
closed-values? (seq (:property/closed-values property))
|
||||
closed-values? (seq (entity-plus/lookup-kv-then-entity property :property/closed-values))
|
||||
default-or-url? (contains? #{:default :url} (:logseq.property/type property))]
|
||||
(cond
|
||||
closed-values?
|
||||
(get-property-value-eid @conn property-id v)
|
||||
(some (fn [item]
|
||||
(when (or (= (:block/title item) v)
|
||||
(= (:logseq.property/value item) v))
|
||||
(:db/id item))) (:block/_closed-value-property property))
|
||||
|
||||
(and default-or-url?
|
||||
;; FIXME: remove this when :logseq.property/order-list-type updated to closed values
|
||||
@@ -308,6 +347,9 @@
|
||||
[conn property-id v property-type]
|
||||
(let [number-property? (= property-type :number)]
|
||||
(cond
|
||||
(and (qualified-keyword? v) (not= :keyword property-type))
|
||||
(db-ident->eid @conn v)
|
||||
|
||||
(and (integer? v)
|
||||
(or (not number-property?)
|
||||
;; Allows :number property to use number as a ref (for closed value) or value
|
||||
@@ -317,13 +359,19 @@
|
||||
v
|
||||
|
||||
(= property-type :page)
|
||||
(if (or (string/blank? v) (not (string? v)))
|
||||
(throw (ex-info "Value should be non-empty string" {:property-id property-id
|
||||
:property-type property-type
|
||||
:v v}))
|
||||
(let [error-data {:property-id property-id
|
||||
:property-type property-type
|
||||
:v v}]
|
||||
(if (or (string/blank? v) (not (string? v)))
|
||||
(throw (ex-info "Value should be non-empty string" error-data))
|
||||
(let [page (ldb/get-page @conn v)]
|
||||
(if (entity-util/page? page)
|
||||
(:db/id page)
|
||||
(let [[_ page-uuid] (outliner-page/create! conn v error-data)]
|
||||
(if-not page-uuid
|
||||
(throw (ex-info "Failed to create page" {}))
|
||||
(:db/id (d/entity @conn [:block/uuid page-uuid]))))))))
|
||||
|
||||
;; TODO: create page
|
||||
nil)
|
||||
:else
|
||||
;; only value-ref-property types should call this
|
||||
(when-let [v' (if (and number-property? (string? v))
|
||||
@@ -391,25 +439,32 @@
|
||||
@conn
|
||||
(if (number? v) (d/entity @conn v) v)
|
||||
(map #(d/entity @conn %) block-eids)))
|
||||
_ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
|
||||
_ (when (nil? property)
|
||||
(throw (ex-info (str "Property " property-id " doesn't exist yet") {:property-id property-id})))
|
||||
property-type (get property :logseq.property/type :default)
|
||||
_ (assert (some? v) "Can't set a nil property value must be not nil")
|
||||
entity-id? (and (:entity-id? options) (number? v))
|
||||
ref? (contains? db-property-type/all-ref-property-types property-type)
|
||||
default-url-not-closed? (and (contains? #{:default :url} property-type)
|
||||
(not (seq (:property/closed-values property))))
|
||||
entity-id? (and (:entity-id? options) (number? v))
|
||||
(not (seq (entity-plus/lookup-kv-then-entity property :property/closed-values))))
|
||||
v' (if (and ref? (not entity-id?))
|
||||
(convert-ref-property-value conn property-id v property-type)
|
||||
v)
|
||||
_ (when (nil? v')
|
||||
(throw (ex-info "Property value must be not nil" {:v v})))
|
||||
txs (doall
|
||||
(mapcat
|
||||
(fn [eid]
|
||||
(if-let [block (d/entity @conn eid)]
|
||||
(let [v' (if default-url-not-closed?
|
||||
(let [v (if (number? v) (:block/title (d/entity @conn v)) v)]
|
||||
(convert-ref-property-value conn property-id v property-type))
|
||||
(let [v' (if (and default-url-not-closed?
|
||||
(not (and (keyword? v) entity-id?)))
|
||||
(do
|
||||
(when (number? v')
|
||||
(throw-error-if-invalid-property-value @conn property v'))
|
||||
(let [v (if (number? v') (:block/title (d/entity @conn v')) v')]
|
||||
(convert-ref-property-value conn property-id v property-type)))
|
||||
v')]
|
||||
(throw-error-if-self-value block v' ref?)
|
||||
(throw-error-if-invalid-property-value @conn property v')
|
||||
(build-property-value-tx-data conn block property-id v'))
|
||||
(js/console.error "Skipping setting a block's property because the block id could not be found:" eid)))
|
||||
block-eids))]
|
||||
@@ -462,37 +517,49 @@
|
||||
attributes as properties"
|
||||
[conn block-eid property-id v]
|
||||
(throw-error-if-read-only-property property-id)
|
||||
(if (nil? v)
|
||||
(remove-block-property! conn block-eid property-id)
|
||||
(let [block-eid (->eid block-eid)
|
||||
_ (assert (qualified-keyword? property-id) "property-id should be a keyword")
|
||||
block (d/entity @conn block-eid)
|
||||
db-attribute? (some? (db-schema/schema property-id))]
|
||||
(when (= property-id :block/tags)
|
||||
(outliner-validate/validate-tags-property @conn [block-eid] v))
|
||||
(when (= property-id :logseq.property.class/extends)
|
||||
(outliner-validate/validate-extends-property @conn v [block]))
|
||||
(cond
|
||||
db-attribute?
|
||||
(when-not (and (= property-id :block/alias) (= v (:db/id block))) ; alias can't be itself
|
||||
(let [tx-data (cond->
|
||||
[{:db/id (:db/id block) property-id v}]
|
||||
(= property-id :logseq.property.class/extends)
|
||||
(conj [:db/retract (:db/id block) :logseq.property.class/extends :logseq.class/Root]))]
|
||||
(ldb/transact! conn tx-data
|
||||
{:outliner-op :save-block})))
|
||||
:else
|
||||
(let [property (d/entity @conn property-id)
|
||||
_ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
|
||||
property-type (get property :logseq.property/type :default)
|
||||
ref? (db-property-type/all-ref-property-types property-type)
|
||||
new-value (if ref?
|
||||
(convert-ref-property-value conn property-id v property-type)
|
||||
v)
|
||||
existing-value (get block property-id)]
|
||||
(throw-error-if-self-value block new-value ref?)
|
||||
(when-not (= existing-value new-value)
|
||||
(raw-set-block-property! conn block property property-type new-value)))))))
|
||||
(let [db @conn
|
||||
block-eid (->eid block-eid)
|
||||
_ (assert (qualified-keyword? property-id) "property-id should be a keyword")
|
||||
block (d/entity @conn block-eid)
|
||||
db-attribute? (some? (db-schema/schema property-id))
|
||||
property (d/entity @conn property-id)
|
||||
property-type (get property :logseq.property/type :default)
|
||||
ref? (db-property-type/all-ref-property-types property-type)
|
||||
v' (if ref?
|
||||
(convert-ref-property-value conn property-id v property-type)
|
||||
v)]
|
||||
(when-not (and block property)
|
||||
(throw (ex-info "Set block property failed: block or property doesn't exist"
|
||||
{:block-eid block-eid
|
||||
:property-id property-id
|
||||
:block block
|
||||
:property property})))
|
||||
(if (nil? v')
|
||||
(remove-block-property! conn block-eid property-id)
|
||||
(do
|
||||
(when (= property-id :block/tags)
|
||||
(outliner-validate/validate-tags-property @conn [block-eid] v'))
|
||||
(when (= property-id :logseq.property.class/extends)
|
||||
(outliner-validate/validate-extends-property @conn v' [block]))
|
||||
(cond
|
||||
db-attribute?
|
||||
(do
|
||||
(throw-error-if-invalid-property-value db property v')
|
||||
(when-not (and (= property-id :block/alias) (= v' (:db/id block))) ; alias can't be itself
|
||||
(let [tx-data (cond->
|
||||
[{:db/id (:db/id block) property-id v'}]
|
||||
(= property-id :logseq.property.class/extends)
|
||||
(conj [:db/retract (:db/id block) :logseq.property.class/extends :logseq.class/Root]))]
|
||||
(ldb/transact! conn tx-data
|
||||
{:outliner-op :save-block}))))
|
||||
:else
|
||||
(let [_ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
|
||||
ref? (db-property-type/all-ref-property-types property-type)
|
||||
existing-value (get block property-id)]
|
||||
(throw-error-if-self-value block v' ref?)
|
||||
|
||||
(when-not (= existing-value v')
|
||||
(raw-set-block-property! conn block property v'))))))))
|
||||
|
||||
(defn upsert-property!
|
||||
"Updates property if property-id is given. Otherwise creates a property
|
||||
@@ -508,6 +575,10 @@
|
||||
:payload {:message "Property failed to create. Please try a different property name."
|
||||
:type :error}})))))]
|
||||
(assert (qualified-keyword? db-ident))
|
||||
(when (and (contains? #{:checkbox} (:logseq.property/type schema))
|
||||
(= :db.cardinality/many (:db/cardinality schema)))
|
||||
(throw (ex-info ":checkbox property doesn't allow multiple values" {:property-id property-id
|
||||
:schema schema})))
|
||||
(if-let [property (and (qualified-keyword? property-id) (d/entity db db-ident))]
|
||||
(update-property conn db-ident property schema opts)
|
||||
(let [k-name (or (and property-name (name property-name))
|
||||
@@ -651,7 +722,7 @@
|
||||
{:block/title resolved-value})))
|
||||
icon
|
||||
(assoc :logseq.property/icon icon))]
|
||||
(let [max-order (:block/order (last (:property/closed-values property)))
|
||||
(let [max-order (:block/order (last (entity-plus/lookup-kv-then-entity property :property/closed-values)))
|
||||
new-block (-> (db-property-build/build-closed-value-block block-id nil resolved-value
|
||||
property {:icon icon})
|
||||
(assoc :block/order (db-order/gen-key max-order nil)))]
|
||||
@@ -738,6 +809,11 @@
|
||||
(defn delete-closed-value!
|
||||
"Returns true when deleted or if not deleted displays warning and returns false"
|
||||
[conn property-id value-block-id]
|
||||
(when (or (nil? property-id)
|
||||
(nil? value-block-id))
|
||||
(throw (ex-info "empty property-id or value-block-id when delete-closed-value!"
|
||||
{:property-id property-id
|
||||
:value-block-id value-block-id})))
|
||||
(when-let [value-block (d/entity @conn value-block-id)]
|
||||
(if (ldb/built-in? value-block)
|
||||
(throw (ex-info "The choice can't be deleted"
|
||||
|
||||
@@ -301,8 +301,8 @@
|
||||
[{:page {:block/title "page1"}
|
||||
:blocks [{:block/title "b1" :user.property/default [:block/uuid used-closed-value-uuid]}]}]})
|
||||
_ (assert (:user.property/default (db-test/find-block-by-content @conn "b1")))
|
||||
property-uuid (:block/uuid (d/entity @conn :user.property-default))
|
||||
_ (outliner-property/delete-closed-value! conn property-uuid [:block/uuid closed-value-uuid])]
|
||||
property-id (:db/id (d/entity @conn :user.property/default))
|
||||
_ (outliner-property/delete-closed-value! conn property-id [:block/uuid closed-value-uuid])]
|
||||
(is (nil? (d/entity @conn [:block/uuid closed-value-uuid])))))
|
||||
|
||||
(deftest class-add-property!
|
||||
@@ -367,4 +367,4 @@
|
||||
(:db/id (d/entity @conn :user.class/C1)))
|
||||
(is (= [:logseq.class/Root]
|
||||
(:logseq.property.class/extends (db-test/readable-properties (d/entity @conn :user.class/C3))))
|
||||
"Extends property is restored back to Root")))
|
||||
"Extends property is restored back to Root")))
|
||||
|
||||
6
deps/outliner/yarn.lock
vendored
6
deps/outliner/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
2
deps/publishing/package.json
vendored
2
deps/publishing/package.json
vendored
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28",
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29",
|
||||
"mldoc": "^1.5.9"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
6
deps/publishing/yarn.lock
vendored
6
deps/publishing/yarn.lock
vendored
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
@@ -703,6 +703,8 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
opts?: Partial<{
|
||||
before: boolean
|
||||
sibling: boolean
|
||||
start: boolean
|
||||
end: boolean
|
||||
isPageBlock: boolean
|
||||
focus: boolean
|
||||
customUUID: string
|
||||
@@ -774,6 +776,9 @@ export interface IEditorProxy extends Record<string, any> {
|
||||
renamePage: (oldName: string, newName: string) => Promise<void>
|
||||
|
||||
getAllPages: (repo?: string) => Promise<PageEntity[] | null>
|
||||
getAllTags: () => Promise<PageEntity[] | null>
|
||||
getAllProperties: () => Promise<PageEntity[] | null>
|
||||
getTagObjects: (PageIdentity) => Promise<BlockEntity[] | null>
|
||||
|
||||
prependBlockInPage: (
|
||||
page: PageIdentity,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v28"
|
||||
"@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v29"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "11.10.0",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v28":
|
||||
version "1.2.173-feat-db-v28"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/c0410ff81a8b0d510705581cccd39788d862dc91"
|
||||
"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v29":
|
||||
version "1.2.173-feat-db-v29"
|
||||
resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/6c4f8eec72a0a5b0c7b96c32cc73f86b045305c5"
|
||||
dependencies:
|
||||
import-meta-resolve "^4.1.0"
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
(string/replace-first "${HOST}" HOST)
|
||||
(string/replace-first "${PORT}" PORT))]
|
||||
(doto rep (.type "text/html")
|
||||
(.send html))))))
|
||||
(.send html))))))
|
||||
;; listen port
|
||||
_ (.listen s (bean/->js (select-keys @*state [:host :port])))]
|
||||
(reset! *server s)
|
||||
|
||||
@@ -341,7 +341,7 @@
|
||||
(let [page-entity (db/entity [:block/uuid page])
|
||||
repo (state/sub :git/current-repo)
|
||||
format (get page-entity :block/format :markdown)
|
||||
block (db-model/query-block-by-uuid uuid)
|
||||
block (db-model/get-block-by-uuid uuid)
|
||||
content (:block/title block)]
|
||||
(when-not (string/blank? content)
|
||||
[:.py-2 (search/block-search-result-item repo uuid format content q :block)])))
|
||||
|
||||
@@ -174,10 +174,7 @@
|
||||
(defn <get-block-refs
|
||||
[graph eid]
|
||||
(assert (integer? eid))
|
||||
(p/let [result (state/<invoke-db-worker :thread-api/get-block-refs graph eid)
|
||||
conn (db/get-db graph false)
|
||||
_ (d/transact! conn result)]
|
||||
result))
|
||||
(state/<invoke-db-worker :thread-api/get-block-refs graph eid))
|
||||
|
||||
(defn <get-block-refs-count
|
||||
[graph eid]
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
(ns frontend.db.model
|
||||
"Core db functions."
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
(:require [clojure.string :as string]
|
||||
[clojure.walk :as walk]
|
||||
[datascript.core :as d]
|
||||
[frontend.common.graph-view :as graph-view]
|
||||
[frontend.config :as config]
|
||||
[frontend.date :as date]
|
||||
[frontend.db.conn :as conn]
|
||||
[frontend.db.file-based.model :as file-model]
|
||||
[frontend.db.react :as react]
|
||||
[frontend.db.utils :as db-utils]
|
||||
[frontend.state :as state]
|
||||
@@ -370,32 +368,6 @@ independent of format as format specific heading characters are stripped"
|
||||
(when-let [db (conn/get-db repo)]
|
||||
(graph-view/get-pages-that-mentioned-page db page-id include-journals?)))
|
||||
|
||||
(defn get-page-referenced-blocks-full
|
||||
([page-id]
|
||||
(get-page-referenced-blocks-full (state/get-current-repo) page-id))
|
||||
([repo page-id]
|
||||
(when (and repo page-id)
|
||||
(when-let [db (conn/get-db repo)]
|
||||
(let [pages (page-alias-set repo page-id)
|
||||
aliases (set/difference pages #{page-id})]
|
||||
(->>
|
||||
(d/q
|
||||
'[:find [(pull ?block ?block-attrs) ...]
|
||||
:in $ [?ref-page ...] ?block-attrs
|
||||
:where
|
||||
[?r :block/name ?ref-page]
|
||||
[?block :block/refs ?r]]
|
||||
db
|
||||
pages
|
||||
(butlast file-model/file-graph-block-attrs))
|
||||
(remove (fn [block] (= page-id (:db/id (:block/page block)))))
|
||||
db-utils/group-by-page
|
||||
(map (fn [[k blocks]]
|
||||
(let [k (if (contains? aliases (:db/id k))
|
||||
(assoc k :block/alias? true)
|
||||
k)]
|
||||
[k blocks])))))))))
|
||||
|
||||
(defn get-referenced-blocks
|
||||
([eid]
|
||||
(get-referenced-blocks (state/get-current-repo) eid))
|
||||
@@ -416,14 +388,6 @@ independent of format as format specific heading characters are stripped"
|
||||
(some? (get block (:db/ident entity))))))
|
||||
(util/distinct-by :db/id)))))))
|
||||
|
||||
(defn get-block-referenced-blocks
|
||||
[block-id]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when (conn/get-db repo)
|
||||
(->> (get-referenced-blocks repo block-id)
|
||||
(sort-by-order-recursive)
|
||||
db-utils/group-by-page))))
|
||||
|
||||
(defn journal-page?
|
||||
"sanitized page-name only"
|
||||
[page-name]
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.repo :as repo-handler]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.modules.outliner.op :as outliner-op]
|
||||
[frontend.modules.outliner.ui :as ui-outliner-tx]
|
||||
[frontend.persist-db :as persist-db]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
@@ -14,9 +16,7 @@
|
||||
[logseq.db.sqlite.export :as sqlite-export]
|
||||
[logseq.db.sqlite.util :as sqlite-util]
|
||||
[logseq.shui.ui :as shui]
|
||||
[promesa.core :as p]
|
||||
[frontend.modules.outliner.ui :as ui-outliner-tx]
|
||||
[frontend.modules.outliner.op :as outliner-op]))
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn import-from-sqlite-db!
|
||||
[buffer bare-graph-name finished-ok-handler]
|
||||
|
||||
@@ -556,7 +556,9 @@
|
||||
(state/set-state! :editor/async-unsaved-chars nil))))))
|
||||
|
||||
(defn api-insert-new-block!
|
||||
[content {:keys [page block-uuid sibling? before? properties
|
||||
[content {:keys [page block-uuid
|
||||
sibling? before? start? end?
|
||||
properties
|
||||
custom-uuid replace-empty-target? edit-block? ordered-list? other-attrs]
|
||||
:or {sibling? false
|
||||
before? false
|
||||
@@ -598,6 +600,7 @@
|
||||
(wrap-parse-block)
|
||||
(assoc :block/uuid (or custom-uuid (db/new-block-id))))
|
||||
new-block (merge new-block other-attrs)
|
||||
block' (db/entity (:db/id block))
|
||||
[target-block sibling?] (cond
|
||||
before?
|
||||
(let [left-or-parent (or (ldb/get-left-sibling block)
|
||||
@@ -607,13 +610,21 @@
|
||||
[left-or-parent sibling?])
|
||||
|
||||
sibling?
|
||||
[(db/entity (:db/id block)) sibling?]
|
||||
[block' sibling?]
|
||||
|
||||
start?
|
||||
[block' false]
|
||||
|
||||
end?
|
||||
(if last-block
|
||||
[block' false]
|
||||
[last-block true])
|
||||
|
||||
last-block
|
||||
[last-block true]
|
||||
|
||||
block
|
||||
[(db/entity (:db/id block)) sibling?]
|
||||
[block' sibling?]
|
||||
|
||||
;; FIXME: assert
|
||||
:else
|
||||
@@ -2064,12 +2075,11 @@
|
||||
(when-not keep-uuid? [:id])
|
||||
[:custom_id :custom-id]
|
||||
exclude-properties))
|
||||
:block/format format)
|
||||
(not db-based?)
|
||||
(assoc :block/properties-text-values (apply dissoc (:block/properties-text-values block)
|
||||
:block/properties-text-values (apply dissoc (:block/properties-text-values block)
|
||||
(concat
|
||||
(when-not keep-uuid? [:id])
|
||||
exclude-properties)))))))
|
||||
exclude-properties))
|
||||
:block/format format)))))
|
||||
|
||||
(defn- edit-last-block-after-inserted!
|
||||
[result]
|
||||
@@ -2179,17 +2189,15 @@
|
||||
A block element: {:content :properties :children [block-1, block-2, ...]}"
|
||||
[tree-vec format {:keys [target-block keep-uuid?] :as opts}]
|
||||
(let [repo (state/get-current-repo)
|
||||
page-id (:db/id (:block/page target-block))
|
||||
page-id (or (:db/id (:block/page target-block))
|
||||
(when (ldb/page? target-block)
|
||||
(:db/id target-block)))
|
||||
page-name (some-> page-id (db/entity) :block/name)
|
||||
blocks (block-tree->blocks repo tree-vec format keep-uuid? page-name)
|
||||
blocks (gp-block/with-parent-and-order page-id blocks)
|
||||
block-refs (->> (mapcat :block/refs blocks)
|
||||
(set)
|
||||
(filter (fn [ref] (and (vector? ref) (= :block/uuid (first ref))))))]
|
||||
blocks (gp-block/with-parent-and-order page-id blocks)]
|
||||
|
||||
(ui-outliner-tx/transact!
|
||||
{:outliner-op :paste-blocks}
|
||||
(when (seq block-refs)
|
||||
(db/transact! (map (fn [[_ id]] {:block/uuid id}) block-refs)))
|
||||
(paste-blocks blocks (merge opts {:ops-only? true})))))
|
||||
|
||||
(defn insert-block-tree-after-target
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
:deleted-shapes deleted-shapes
|
||||
:new-shapes created-shapes
|
||||
:metadata {:whiteboard/transact? true
|
||||
:pipeline-replace? replace?}})))
|
||||
:whiteboard/replace? replace?}})))
|
||||
|
||||
(defonce *last-shapes-nonce (atom {}))
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
[{:keys [repo tx-meta tx-data deleted-block-uuids deleted-assets affected-keys blocks]}]
|
||||
;; (prn :debug
|
||||
;; :tx-meta tx-meta
|
||||
;; ;; :tx-data tx-data
|
||||
;; )
|
||||
;; :tx-data tx-data)
|
||||
(let [{:keys [from-disk? new-graph? initial-pages? end?]} tx-meta
|
||||
tx-report {:tx-meta tx-meta
|
||||
:tx-data tx-data}]
|
||||
|
||||
@@ -269,8 +269,7 @@
|
||||
(when (seq tx-data)
|
||||
(let [reversed-tx-data (get-reversed-datoms conn undo? data tx-meta)
|
||||
tx-meta' (-> tx-meta
|
||||
(dissoc :pipeline-replace?
|
||||
:batch-tx/batch-tx-mode?)
|
||||
(dissoc :batch-tx/batch-tx-mode?)
|
||||
(assoc
|
||||
:gen-undo-ops? false
|
||||
:undo? undo?
|
||||
|
||||
@@ -149,15 +149,16 @@
|
||||
(tc/to-long next-time)))))
|
||||
|
||||
(defn- compute-reschedule-property-tx
|
||||
[conn db entity property-ident]
|
||||
(let [frequency (or (db-property/property-value-content (:logseq.property.repeat/recur-frequency entity))
|
||||
(let [property (d/entity db :logseq.property.repeat/recur-frequency)
|
||||
default-value-block (db-property-build/build-property-value-block property property 1)
|
||||
default-value-tx-data [default-value-block
|
||||
{:db/id (:db/id property)
|
||||
:logseq.property/default-value [:block/uuid (:block/uuid default-value-block)]}]]
|
||||
(d/transact! conn default-value-tx-data)
|
||||
1))
|
||||
[db entity property-ident]
|
||||
(let [[frequency default-value-tx-data]
|
||||
(or [(db-property/property-value-content (:logseq.property.repeat/recur-frequency entity))
|
||||
nil]
|
||||
(let [property (d/entity db :logseq.property.repeat/recur-frequency)
|
||||
default-value-block (db-property-build/build-property-value-block property property 1)
|
||||
default-value-tx-data [default-value-block
|
||||
{:db/id (:db/id property)
|
||||
:logseq.property/default-value [:block/uuid (:block/uuid default-value-block)]}]]
|
||||
[1 default-value-tx-data]))
|
||||
unit (:logseq.property.repeat/recur-unit entity)
|
||||
property (d/entity db property-ident)
|
||||
date? (= :date (:logseq.property/type property))
|
||||
@@ -175,11 +176,12 @@
|
||||
(outliner-page/create db title {})))
|
||||
value (if date? [:block/uuid page-uuid] next-time-long)]
|
||||
(concat
|
||||
default-value-tx-data
|
||||
tx-data
|
||||
(when value
|
||||
[[:db/add (:db/id entity) property-ident value]])))))))
|
||||
|
||||
(defmethod handle-command :reschedule [_ conn db entity _datoms]
|
||||
(defmethod handle-command :reschedule [_ db entity _datoms]
|
||||
(let [property-ident (or (:db/ident (:logseq.property.repeat/temporal-property entity))
|
||||
:logseq.property/scheduled)
|
||||
other-property-idents (cond
|
||||
@@ -193,14 +195,14 @@
|
||||
|
||||
:else
|
||||
(filter (fn [p] (get entity p)) [:logseq.property/deadline :logseq.property/scheduled]))]
|
||||
(mapcat #(compute-reschedule-property-tx conn db entity %) (distinct (cons property-ident other-property-idents)))))
|
||||
(mapcat #(compute-reschedule-property-tx db entity %) (distinct (cons property-ident other-property-idents)))))
|
||||
|
||||
(defmethod handle-command :set-property [_ _db _conn entity _datoms property value]
|
||||
(defmethod handle-command :set-property [_ _db entity _datoms property value]
|
||||
(let [property' (get-property entity property)
|
||||
value' (get-value entity property value)]
|
||||
[[:db/add (:db/id entity) property' value']]))
|
||||
|
||||
(defmethod handle-command :record-property-history [_ _conn db entity datoms]
|
||||
(defmethod handle-command :record-property-history [_ db entity datoms]
|
||||
(let [changes (keep (fn [d]
|
||||
(let [property (d/entity db (:a d))]
|
||||
(when (and (true? (get property :logseq.property/enable-history?))
|
||||
@@ -218,7 +220,7 @@
|
||||
:logseq.property.history/property (:db/id property)})))
|
||||
changes)))
|
||||
|
||||
(defmethod handle-command :default [command _conn _db entity datoms]
|
||||
(defmethod handle-command :default [command _db entity datoms]
|
||||
(throw (ex-info "Unhandled command"
|
||||
{:command command
|
||||
:entity entity
|
||||
@@ -226,23 +228,22 @@
|
||||
|
||||
(defn execute-command
|
||||
"Build tx-data"
|
||||
[conn db entity datoms [_command {:keys [actions]}]]
|
||||
[db entity datoms [_command {:keys [actions]}]]
|
||||
(mapcat (fn [action]
|
||||
(apply handle-command (first action) conn db entity datoms (rest action))) actions))
|
||||
(apply handle-command (first action) db entity datoms (rest action))) actions))
|
||||
|
||||
(defn run-commands
|
||||
[conn {:keys [tx-data db-after]}]
|
||||
(let [db db-after]
|
||||
(mapcat (fn [[e datoms]]
|
||||
(let [entity (d/entity db e)
|
||||
commands (filter (fn [[_command {:keys [entity-conditions tx-conditions]}]]
|
||||
(and
|
||||
(if (seq entity-conditions)
|
||||
(every? #(satisfy-condition? db entity % nil) entity-conditions)
|
||||
true)
|
||||
(every? #(satisfy-condition? db entity % datoms) tx-conditions))) @*commands)]
|
||||
(mapcat
|
||||
(fn [command]
|
||||
(execute-command conn db entity datoms command))
|
||||
commands)))
|
||||
(group-by :e tx-data))))
|
||||
[{:keys [tx-data db-after]}]
|
||||
(mapcat (fn [[e datoms]]
|
||||
(let [entity (d/entity db-after e)
|
||||
commands (filter (fn [[_command {:keys [entity-conditions tx-conditions]}]]
|
||||
(and
|
||||
(if (seq entity-conditions)
|
||||
(every? #(satisfy-condition? db-after entity % nil) entity-conditions)
|
||||
true)
|
||||
(every? #(satisfy-condition? db-after entity % datoms) tx-conditions))) @*commands)]
|
||||
(mapcat
|
||||
(fn [command]
|
||||
(execute-command db-after entity datoms command))
|
||||
commands)))
|
||||
(group-by :e tx-data)))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
(:require [datascript.core :as d]
|
||||
[frontend.common.thread-api :refer [def-thread-api]]
|
||||
[frontend.worker.state :as worker-state]
|
||||
[logseq.db :as ldb]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defonce ^:private encoder (new js/TextEncoder "utf-8"))
|
||||
@@ -115,7 +116,7 @@
|
||||
(assert (some? conn) repo)
|
||||
(let [aes-key-datom (first (d/datoms @conn :avet :aes-key-jwk))]
|
||||
(assert (nil? aes-key-datom) aes-key-datom)
|
||||
(d/transact! conn [[:db/add "e1" :aes-key-jwk aes-key-jwk]]))))
|
||||
(ldb/transact! conn [[:db/add "e1" :aes-key-jwk aes-key-jwk]]))))
|
||||
|
||||
(defn get-graph-keys-jwk
|
||||
[repo]
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
(fn [{:keys [entity dispatch-key]}]
|
||||
(let [entity (d/entity db (:db/id entity))]
|
||||
(cond
|
||||
(and (:block/tx-id entity) (nil? (:block/title entity)))
|
||||
[[:db/retractEntity (:db/id entity)]]
|
||||
(= :block/path-refs (:db/ident entity))
|
||||
(concat [[:db/retractEntity (:db/id entity)]]
|
||||
(try
|
||||
@@ -101,6 +103,10 @@
|
||||
(let [db @conn
|
||||
{:keys [errors datom-count entities]} (db-validate/validate-db! db)
|
||||
invalid-entity-ids (distinct (map (fn [e] (:db/id (:entity e))) errors))]
|
||||
|
||||
(doseq [id invalid-entity-ids]
|
||||
(prn :debug :id id :entity (into {} (d/entity db id))))
|
||||
|
||||
(if errors
|
||||
(do
|
||||
(fix-invalid-blocks! conn errors)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
(when-not from-disk?
|
||||
(p/do!
|
||||
;; Sync SQLite search
|
||||
;; Sync SQLite search
|
||||
(let [{:keys [blocks-to-remove-set blocks-to-add]} (search/sync-search-indice repo tx-report')]
|
||||
(when (seq blocks-to-remove-set)
|
||||
((@thread-api/*thread-apis :thread-api/search-delete-blocks) repo blocks-to-remove-set))
|
||||
@@ -65,9 +65,7 @@
|
||||
(map (fn [id] [:db/add id :logseq.property.embedding/hnsw-label-updated-at 0])))
|
||||
tx-data (concat remove-old-hnsw-tx-data mark-embedding-tx-data)]
|
||||
(when (seq tx-data)
|
||||
(d/transact! conn tx-data
|
||||
{:skip-refresh? true
|
||||
:pipeline-replace? true})))))
|
||||
(ldb/transact! conn tx-data {})))))
|
||||
|
||||
(defn listen-db-changes!
|
||||
[repo conn & {:keys [handler-keys]}]
|
||||
@@ -90,39 +88,37 @@
|
||||
(remove-old-embeddings-and-reset-new-updates! conn tx-data tx-meta)
|
||||
|
||||
(let [tx-meta (merge (batch-tx/get-batch-opts) tx-meta)
|
||||
pipeline-replace? (:pipeline-replace? tx-meta)
|
||||
in-batch-tx-mode? (:batch-tx/batch-tx-mode? tx-meta)]
|
||||
(when-not pipeline-replace?
|
||||
(when in-batch-tx-mode?
|
||||
(batch-tx/set-batch-opts (dissoc tx-meta :pipeline-replace?)))
|
||||
(cond
|
||||
(and in-batch-tx-mode?
|
||||
(not (:batch-tx/exit? tx-meta)))
|
||||
(when in-batch-tx-mode?
|
||||
(batch-tx/set-batch-opts tx-meta))
|
||||
(cond
|
||||
(and in-batch-tx-mode?
|
||||
(not (:batch-tx/exit? tx-meta)))
|
||||
;; still in batch mode
|
||||
(vswap! *batch-all-txs into tx-data)
|
||||
(vswap! *batch-all-txs into tx-data)
|
||||
|
||||
in-batch-tx-mode?
|
||||
in-batch-tx-mode?
|
||||
;; exit batch mode
|
||||
(when-let [tx-data (not-empty (get-batch-txs))]
|
||||
(vreset! *batch-all-txs [])
|
||||
(let [db-before (batch-tx/get-batch-db-before)
|
||||
tx-meta (dissoc tx-meta :batch-tx/batch-tx-mode? :batch-tx/exit?)
|
||||
tx-report (assoc tx-report
|
||||
:tx-data tx-data
|
||||
:db-before db-before
|
||||
:tx-meta tx-meta)
|
||||
tx-report' (if sync-db-to-main-thread?
|
||||
(sync-db-to-main-thread repo conn tx-report)
|
||||
tx-report)
|
||||
opt (assoc (additional-args (:tx-data tx-report')) :repo repo)]
|
||||
(doseq [[k handler-fn] handlers]
|
||||
(handler-fn k opt tx-report'))))
|
||||
|
||||
(seq tx-data)
|
||||
;; raw transact
|
||||
(let [tx-report' (if sync-db-to-main-thread?
|
||||
(when-let [tx-data (not-empty (get-batch-txs))]
|
||||
(vreset! *batch-all-txs [])
|
||||
(let [db-before (batch-tx/get-batch-db-before)
|
||||
tx-meta (dissoc tx-meta :batch-tx/batch-tx-mode? :batch-tx/exit?)
|
||||
tx-report (assoc tx-report
|
||||
:tx-data tx-data
|
||||
:db-before db-before
|
||||
:tx-meta tx-meta)
|
||||
tx-report' (if sync-db-to-main-thread?
|
||||
(sync-db-to-main-thread repo conn tx-report)
|
||||
tx-report)
|
||||
opt (assoc (additional-args (:tx-data tx-report')) :repo repo)]
|
||||
(doseq [[k handler-fn] handlers]
|
||||
(handler-fn k opt tx-report')))))))))))
|
||||
(handler-fn k opt tx-report'))))
|
||||
|
||||
(seq tx-data)
|
||||
;; raw transact
|
||||
(let [tx-report' (if sync-db-to-main-thread?
|
||||
(sync-db-to-main-thread repo conn tx-report)
|
||||
tx-report)
|
||||
opt (assoc (additional-args (:tx-data tx-report')) :repo repo)]
|
||||
(doseq [[k handler-fn] handlers]
|
||||
(handler-fn k opt tx-report'))))))))))
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
[frontend.worker.file.reset :as file-reset]
|
||||
[frontend.worker.handler.page :as worker-page]
|
||||
[frontend.worker.handler.page.file-based.rename :as file-worker-page-rename]
|
||||
[frontend.worker.pipeline :as worker-pipeline]
|
||||
[frontend.worker.rtc.asset-db-listener]
|
||||
[frontend.worker.rtc.client-op :as client-op]
|
||||
[frontend.worker.rtc.core :as rtc.core]
|
||||
@@ -40,10 +41,13 @@
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.entity-plus :as entity-plus]
|
||||
[logseq.db.common.entity-util :as common-entity-util]
|
||||
[logseq.db.common.initial-data :as common-initial-data]
|
||||
[logseq.db.common.order :as db-order]
|
||||
[logseq.db.common.reference :as db-reference]
|
||||
[logseq.db.common.sqlite :as common-sqlite]
|
||||
[logseq.db.common.view :as db-view]
|
||||
[logseq.db.frontend.class :as db-class]
|
||||
[logseq.db.frontend.schema :as db-schema]
|
||||
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
|
||||
[logseq.db.sqlite.export :as sqlite-export]
|
||||
@@ -252,8 +256,8 @@
|
||||
(doseq [db (if @*publishing? [sqlite-db] [sqlite-db client-ops-db])]
|
||||
(sqlite-gc/gc-kvs-table! db {:full-gc? full-gc?})
|
||||
(.exec db "VACUUM"))
|
||||
(d/transact! datascript-conn [{:db/ident :logseq.kv/graph-last-gc-at
|
||||
:kv/value (common-util/time-ms)}]))))
|
||||
(ldb/transact! datascript-conn [{:db/ident :logseq.kv/graph-last-gc-at
|
||||
:kv/value (common-util/time-ms)}]))))
|
||||
|
||||
(defn- create-or-open-db!
|
||||
[repo {:keys [config datoms] :as opts}]
|
||||
@@ -271,6 +275,9 @@
|
||||
(common-sqlite/create-kvs-table! db)
|
||||
(when-not @*publishing? (common-sqlite/create-kvs-table! client-ops-db))
|
||||
(search/create-tables-and-triggers! search-db)
|
||||
(ldb/register-transact-pipeline-fn!
|
||||
(fn [tx-report]
|
||||
(worker-pipeline/transact-pipeline repo tx-report)))
|
||||
(let [schema (ldb/get-schema repo)
|
||||
conn (common-sqlite/get-storage-conn storage schema)
|
||||
_ (db-fix/check-and-fix-schema! repo conn)
|
||||
@@ -292,7 +299,7 @@
|
||||
(let [config (or config "")
|
||||
initial-data (sqlite-create-graph/build-db-initial-data
|
||||
config (select-keys opts [:import-type :graph-git-sha]))]
|
||||
(d/transact! conn initial-data {:initial-db? true})))
|
||||
(ldb/transact! conn initial-data {:initial-db? true})))
|
||||
|
||||
(gc-sqlite-dbs! db client-ops-db conn {})
|
||||
|
||||
@@ -461,7 +468,9 @@
|
||||
(def-thread-api :thread-api/get-block-refs
|
||||
[repo id]
|
||||
(when-let [conn (worker-state/get-datascript-conn repo)]
|
||||
(ldb/get-block-refs @conn id)))
|
||||
(->> (db-reference/get-linked-references @conn id)
|
||||
:ref-blocks
|
||||
(map (fn [b] (assoc (into {} b) :db/id (:db/id b)))))))
|
||||
|
||||
(def-thread-api :thread-api/get-block-refs-count
|
||||
[repo id]
|
||||
@@ -627,7 +636,9 @@
|
||||
{:keys [type payload]} (when (map? data) data)]
|
||||
(case type
|
||||
:notification
|
||||
(shared-service/broadcast-to-clients! :notification [(:message payload) (:type payload)])
|
||||
(do
|
||||
(log/error ::apply-outliner-ops-failed e)
|
||||
(shared-service/broadcast-to-clients! :notification [(:message payload) (:type payload)]))
|
||||
(throw e)))))))
|
||||
|
||||
(def-thread-api :thread-api/file-writes-finished?
|
||||
@@ -697,6 +708,12 @@
|
||||
(let [db @(worker-state/get-datascript-conn repo)]
|
||||
(db-view/get-view-data db view-id option)))
|
||||
|
||||
(def-thread-api :thread-api/get-class-objects
|
||||
[repo class-id]
|
||||
(let [db @(worker-state/get-datascript-conn repo)]
|
||||
(->> (db-class/get-class-objects db class-id)
|
||||
(map common-entity-util/entity->map))))
|
||||
|
||||
(def-thread-api :thread-api/get-property-values
|
||||
[repo {:keys [property-ident] :as option}]
|
||||
(let [conn (worker-state/get-datascript-conn repo)]
|
||||
@@ -870,9 +887,19 @@
|
||||
(reset! *service [graph service])
|
||||
service)))))
|
||||
|
||||
(defn- notify-invalid-data
|
||||
[{:keys [tx-meta]}]
|
||||
;; don't notify on production when undo/redo failed
|
||||
(when-not (and (or (:undo? tx-meta) (:redo? tx-meta))
|
||||
(not worker-util/dev?))
|
||||
(shared-service/broadcast-to-clients! :notification
|
||||
[["Invalid DB!"] :error])))
|
||||
|
||||
(defn init
|
||||
"web worker entry"
|
||||
[]
|
||||
(ldb/register-transact-invalid-callback-fn! notify-invalid-data)
|
||||
|
||||
(let [proxy-object (->>
|
||||
fns
|
||||
(map
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
(into-array (map :db/id stale-block-chunk))
|
||||
false))
|
||||
tx-data (labels-update-tx-data @conn e+updated-at-coll)]
|
||||
(d/transact! conn tx-data {:skip-refresh? true})
|
||||
(ldb/transact! conn tx-data {:skip-refresh? true})
|
||||
(m/? (task--update-index-info!* repo infer-worker true))
|
||||
(c.m/<? (.write-index! infer-worker repo))))
|
||||
(m/? (task--update-index-info!* repo infer-worker false))))))))
|
||||
@@ -207,7 +207,7 @@
|
||||
(d/datoms @conn :avet :block/title)
|
||||
(map (fn [d]
|
||||
[:db/add (:e d) :logseq.property.embedding/hnsw-label-updated-at 0])))]
|
||||
(d/transact! conn mark-embedding-tx-data {:skip-refresh? true})))
|
||||
(ldb/transact! conn mark-embedding-tx-data {:skip-refresh? true})))
|
||||
|
||||
(embedding-stale-blocks! repo reset-embedding?)))))
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
(when-let [^js infer-worker @worker-state/*infer-worker]
|
||||
(let [conn (worker-state/get-datascript-conn repo)]
|
||||
(when (c.m/<? (.load-model infer-worker model-name))
|
||||
(d/transact! conn [(ldb/kv :logseq.kv/graph-text-embedding-model-name model-name)])
|
||||
(ldb/transact! conn [(ldb/kv :logseq.kv/graph-text-embedding-model-name model-name)])
|
||||
(log/info :loaded-model model-name))))))
|
||||
|
||||
(defn task--search
|
||||
|
||||
@@ -6,19 +6,16 @@
|
||||
[frontend.worker.commands :as commands]
|
||||
[frontend.worker.file :as file]
|
||||
[frontend.worker.react :as worker-react]
|
||||
[frontend.worker.shared-service :as shared-service]
|
||||
[frontend.worker.state :as worker-state]
|
||||
[logseq.common.defkeywords :refer [defkeywords]]
|
||||
[logseq.common.util :as common-util]
|
||||
[logseq.common.util.page-ref :as page-ref]
|
||||
[logseq.common.uuid :as common-uuid]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.entity-plus :as entity-plus]
|
||||
[logseq.db.common.order :as db-order]
|
||||
[logseq.db.common.sqlite :as common-sqlite]
|
||||
[logseq.db.frontend.class :as db-class]
|
||||
[logseq.db.frontend.validate :as db-validate]
|
||||
[logseq.db.sqlite.export :as sqlite-export]
|
||||
[logseq.db.sqlite.util :as sqlite-util]
|
||||
[logseq.graph-parser.exporter :as gp-exporter]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.outliner.datascript-report :as ds-report]
|
||||
@@ -97,44 +94,6 @@
|
||||
(:tx-data result)))))))]
|
||||
tx-data))
|
||||
|
||||
(defkeywords
|
||||
::skip-validate-db? {:doc "tx-meta option, default = false"}
|
||||
::skip-store-conn {:doc "tx-meta option, skip `d/store` on conn. default = false"})
|
||||
|
||||
(defn validate-db!
|
||||
"Validate db is slow, we probably don't want to enable it for production."
|
||||
[repo conn tx-report tx-meta context]
|
||||
(when (and (not (::skip-validate-db? tx-meta false))
|
||||
(or (:dev? context) (:undo? tx-meta) (:redo? tx-meta))
|
||||
(not (:importing? context)) (sqlite-util/db-based-graph? repo))
|
||||
(let [valid? (if (get-in tx-report [:tx-meta :reset-conn!])
|
||||
true
|
||||
(db-validate/validate-tx-report! tx-report (:validate-db-options context)))]
|
||||
(when-not valid?
|
||||
(when (and (or (get-in context [:validate-db-options :fail-invalid?]) worker-util/dev?)
|
||||
;; don't notify on production when undo/redo failed
|
||||
(not (and (not (:dev? context)) (or (:undo? tx-meta) (:redo? tx-meta)))))
|
||||
(shared-service/broadcast-to-clients! :notification
|
||||
[["Invalid DB!"] :error]))
|
||||
(throw (ex-info "Invalid data" {:graph repo})))))
|
||||
|
||||
;; Ensure :block/order is unique for any block that has :block/parent
|
||||
(when false;; (:dev? context)
|
||||
(let [order-datoms (filter (fn [d] (= :block/order (:a d)))
|
||||
(:tx-data tx-report))]
|
||||
(doseq [datom order-datoms]
|
||||
(let [entity (d/entity @conn (:e datom))
|
||||
parent (:block/parent entity)]
|
||||
(when parent
|
||||
(let [children (:block/_parent parent)
|
||||
order-different? (= (count (distinct (map :block/order children))) (count children))]
|
||||
(when-not order-different?
|
||||
(throw (ex-info (str ":block/order is not unique for children blocks, parent id: " (:db/id parent))
|
||||
{:children (->> (map (fn [b] (select-keys b [:db/id :block/title :block/order])) children)
|
||||
(sort-by :block/order))
|
||||
:tx-meta tx-meta
|
||||
:tx-data (:tx-data tx-report)}))))))))))
|
||||
|
||||
(defn- fix-page-tags
|
||||
"Add missing attributes and remove #Page when inserting or updating block/title with inline tags"
|
||||
[{:keys [db-after tx-data tx-meta]}]
|
||||
@@ -158,7 +117,7 @@
|
||||
[:db/add eid :logseq.property.class/extends :logseq.class/Root]
|
||||
[:db/retract eid :block/tags :logseq.class/Page]])))
|
||||
|
||||
;; remove #Page from tags/journals/whitebaords, etc.
|
||||
;; remove #Page from tags/journals/whiteboards, etc.
|
||||
(and (= :block/tags (:a datom))
|
||||
(:added datom)
|
||||
(= (:db/id page-tag) (:v datom)))
|
||||
@@ -210,9 +169,9 @@
|
||||
(apply concat)))))
|
||||
|
||||
(defn- toggle-page-and-block
|
||||
[conn {:keys [db-before db-after tx-data tx-meta]}]
|
||||
[db {:keys [db-before db-after tx-data tx-meta]}]
|
||||
(when-not (rtc-tx-or-download-graph? tx-meta)
|
||||
(let [page-tag (d/entity @conn :logseq.class/Page)
|
||||
(let [page-tag (d/entity db :logseq.class/Page)
|
||||
library-page (ldb/get-library-page db-after)]
|
||||
(mapcat
|
||||
(fn [datom]
|
||||
@@ -361,15 +320,16 @@
|
||||
(nil? created-by-ent) (cons created-by-block))))))
|
||||
|
||||
(defn- compute-extra-tx-data
|
||||
[repo conn tx-report]
|
||||
[repo tx-report]
|
||||
(let [{:keys [db-before db-after tx-data tx-meta]} tx-report
|
||||
db db-after
|
||||
fix-page-tags-tx-data (fix-page-tags tx-report)
|
||||
fix-inline-page-tx-data (fix-inline-built-in-page-classes tx-report)
|
||||
toggle-page-and-block-tx-data (when (empty? fix-inline-page-tx-data)
|
||||
(toggle-page-and-block conn tx-report))
|
||||
(toggle-page-and-block db tx-report))
|
||||
display-blocks-tx-data (add-missing-properties-to-typed-display-blocks db-after tx-data tx-meta)
|
||||
commands-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (rtc-tx-or-download-graph? tx-meta))
|
||||
(commands/run-commands conn tx-report))
|
||||
(commands/run-commands tx-report))
|
||||
insert-templates-tx (when-not (rtc-tx-or-download-graph? tx-meta)
|
||||
(insert-tag-templates repo tx-report))
|
||||
created-by-tx (add-created-by-ref-hook db-before db-after tx-data tx-meta)]
|
||||
@@ -381,108 +341,71 @@
|
||||
fix-page-tags-tx-data
|
||||
fix-inline-page-tx-data)))
|
||||
|
||||
(defn- reverse-tx!
|
||||
[conn tx-data]
|
||||
(let [reversed-tx-data (map (fn [[e a v _tx add?]]
|
||||
(let [op (if add? :db/retract :db/add)]
|
||||
[op e a v])) tx-data)]
|
||||
(d/transact! conn reversed-tx-data {:revert-tx-data? true
|
||||
:gen-undo-ops? false})))
|
||||
|
||||
(defn- undo-tx-data-if-disallowed!
|
||||
[conn {:keys [tx-data tx-meta]}]
|
||||
(when-not (:rtc-download-graph? tx-meta)
|
||||
(let [db @conn
|
||||
page-has-block-parent? (some (fn [d] (and (:added d)
|
||||
(= :block/parent (:a d))
|
||||
(ldb/page? (d/entity db (:e d)))
|
||||
(not (ldb/page? (d/entity db (:v d)))))) tx-data)]
|
||||
;; TODO: add other cases that need to be undo
|
||||
(when page-has-block-parent?
|
||||
(reverse-tx! conn tx-data)
|
||||
(throw (ex-info "Page can't have block as parent"
|
||||
{:type :notification
|
||||
:payload {:message "Page can't have block as parent"
|
||||
:type :warning}
|
||||
:tx-data tx-data}))))))
|
||||
(defn transact-pipeline
|
||||
"Compute extra tx-data and block/refs, should ensure it's a pure function and
|
||||
doesn't call `d/transact!` or `ldb/transact!`."
|
||||
[repo {:keys [db-after tx-meta] :as tx-report}]
|
||||
(let [db-based? (entity-plus/db-based-graph? db-after)
|
||||
extra-tx-data (when db-based?
|
||||
(compute-extra-tx-data repo tx-report))
|
||||
tx-report* (if (seq extra-tx-data)
|
||||
(let [result (d/with db-after extra-tx-data)]
|
||||
(assoc tx-report
|
||||
:tx-data (concat (:tx-data tx-report) (:tx-data result))
|
||||
:db-after (:db-after result)))
|
||||
tx-report)
|
||||
{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report*)
|
||||
deleted-blocks (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report*))
|
||||
deleted-block-ids (set (map :db/id deleted-blocks))
|
||||
blocks' (remove (fn [b] (deleted-block-ids (:db/id b))) blocks)
|
||||
block-refs (when (seq blocks')
|
||||
(rebuild-block-refs repo tx-report* blocks'))
|
||||
tx-id-data (let [db-after (:db-after tx-report*)
|
||||
updated-blocks (remove (fn [b] (contains? deleted-block-ids (:db/id b)))
|
||||
(concat pages blocks))
|
||||
tx-id (get-in tx-report* [:tempids :db/current-tx])]
|
||||
(keep (fn [b]
|
||||
(when-let [db-id (:db/id b)]
|
||||
(when (:block/uuid (d/entity db-after db-id))
|
||||
{:db/id db-id
|
||||
:block/tx-id tx-id}))) updated-blocks))
|
||||
block-refs-tx-id-data (concat block-refs tx-id-data)
|
||||
replace-tx-report (when (seq block-refs-tx-id-data)
|
||||
(d/with (:db-after tx-report*) block-refs-tx-id-data))
|
||||
tx-report' (or replace-tx-report tx-report*)
|
||||
full-tx-data (concat (:tx-data tx-report*)
|
||||
(:tx-data replace-tx-report))]
|
||||
(assoc tx-report'
|
||||
:tx-data full-tx-data
|
||||
:tx-meta tx-meta
|
||||
:db-before (:db-before tx-report)
|
||||
:db-after (or (:db-after tx-report')
|
||||
(:db-after tx-report)))))
|
||||
|
||||
(defn- invoke-hooks-default
|
||||
[repo conn {:keys [tx-meta] :as tx-report} context]
|
||||
;; Notice: don't catch `undo-tx-data-if-disallowed!` since we want it failed immediately
|
||||
(undo-tx-data-if-disallowed! conn tx-report)
|
||||
(try
|
||||
(let [extra-tx-data (when (sqlite-util/db-based-graph? repo)
|
||||
(compute-extra-tx-data repo conn tx-report))
|
||||
tx-report* (if (seq extra-tx-data)
|
||||
(let [result (ldb/transact! conn extra-tx-data {:pipeline-replace? true
|
||||
:outliner-op :pre-hook-invoke
|
||||
:skip-store? true})]
|
||||
(assoc tx-report
|
||||
:tx-data (concat (:tx-data tx-report) (:tx-data result))
|
||||
:db-after (:db-after result)))
|
||||
tx-report)
|
||||
{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report*)
|
||||
(let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)
|
||||
deleted-blocks (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report))
|
||||
_ (when (common-sqlite/local-file-based-graph? repo)
|
||||
(let [page-ids (distinct (map :db/id pages))]
|
||||
(doseq [page-id page-ids]
|
||||
(when (d/entity @conn page-id)
|
||||
(file/sync-to-file repo page-id tx-meta)))))
|
||||
deleted-blocks (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report*))
|
||||
deleted-block-ids (set (map :db/id deleted-blocks))
|
||||
deleted-block-uuids (set (map :block/uuid deleted-blocks))
|
||||
deleted-block-ids (set (map :db/id deleted-blocks))
|
||||
_ (when (seq deleted-block-uuids)
|
||||
(swap! worker-state/*deleted-block-uuid->db-id merge
|
||||
(zipmap (map :block/uuid deleted-blocks)
|
||||
(map :db/id deleted-blocks))))
|
||||
deleted-assets (keep (fn [id]
|
||||
(let [e (d/entity (:db-before tx-report*) id)]
|
||||
(let [e (d/entity (:db-before tx-report) id)]
|
||||
(when (ldb/asset? e)
|
||||
{:block/uuid (:block/uuid e)
|
||||
:ext (:logseq.property.asset/type e)}))) deleted-block-ids)
|
||||
blocks' (remove (fn [b] (deleted-block-ids (:db/id b))) blocks)
|
||||
block-refs (when (seq blocks')
|
||||
(rebuild-block-refs repo tx-report* blocks'))
|
||||
refs-tx-report (when (seq block-refs)
|
||||
(ldb/transact! conn block-refs {:pipeline-replace? true
|
||||
:skip-store? true}))
|
||||
replace-tx (let [db-after (or (:db-after refs-tx-report) (:db-after tx-report*))]
|
||||
(concat
|
||||
;; update block/tx-id
|
||||
(let [updated-blocks (remove (fn [b] (contains? deleted-block-ids (:db/id b)))
|
||||
(concat pages blocks))
|
||||
tx-id (get-in (or refs-tx-report tx-report*) [:tempids :db/current-tx])]
|
||||
(keep (fn [b]
|
||||
(when-let [db-id (:db/id b)]
|
||||
(when (:block/uuid (d/entity db-after db-id))
|
||||
{:db/id db-id
|
||||
:block/tx-id tx-id}))) updated-blocks))))
|
||||
tx-report' (ldb/transact! conn replace-tx {:pipeline-replace? true
|
||||
;; Ensure db persisted
|
||||
:db-persist? true})
|
||||
_ (when-not (:revert-tx-data? tx-meta)
|
||||
(try
|
||||
(validate-db! repo conn tx-report* tx-meta context)
|
||||
(catch :default e
|
||||
(when-not (rtc-tx-or-download-graph? tx-meta)
|
||||
(prn :debug :revert-invalid-tx
|
||||
:tx-meta
|
||||
tx-meta
|
||||
:tx-data
|
||||
(:tx-data tx-report*))
|
||||
(reverse-tx! conn (:tx-data tx-report*)))
|
||||
(throw e))))
|
||||
full-tx-data (concat (:tx-data tx-report*)
|
||||
(:tx-data refs-tx-report)
|
||||
(:tx-data tx-report'))
|
||||
final-tx-report (assoc tx-report'
|
||||
:tx-data full-tx-data
|
||||
:tx-meta tx-meta
|
||||
:db-before (:db-before tx-report)
|
||||
:db-after (or (:db-after tx-report')
|
||||
(:db-after tx-report)))
|
||||
affected-query-keys (when-not (or (:importing? context) (:rtc-download-graph? tx-meta))
|
||||
(worker-react/get-affected-queries-keys final-tx-report))]
|
||||
{:tx-report final-tx-report
|
||||
(worker-react/get-affected-queries-keys tx-report))]
|
||||
{:tx-report tx-report
|
||||
:affected-keys affected-query-keys
|
||||
:deleted-block-uuids deleted-block-uuids
|
||||
:deleted-assets deleted-assets
|
||||
@@ -494,16 +417,14 @@
|
||||
|
||||
(defn invoke-hooks
|
||||
[repo conn {:keys [tx-meta] :as tx-report} context]
|
||||
(when-not (or (:pipeline-replace? tx-meta)
|
||||
(:revert-tx-data? tx-meta))
|
||||
(let [{:keys [from-disk? new-graph?]} tx-meta]
|
||||
(cond
|
||||
(or from-disk? new-graph?)
|
||||
{:tx-report tx-report}
|
||||
(let [{:keys [from-disk? new-graph?]} tx-meta]
|
||||
(cond
|
||||
(or from-disk? new-graph?)
|
||||
{:tx-report tx-report}
|
||||
|
||||
(or (::gp-exporter/new-graph? tx-meta)
|
||||
(and (::sqlite-export/imported-data? tx-meta) (:import-db? tx-meta)))
|
||||
(invoke-hooks-for-imported-graph conn tx-report)
|
||||
(or (::gp-exporter/new-graph? tx-meta)
|
||||
(and (::sqlite-export/imported-data? tx-meta) (:import-db? tx-meta)))
|
||||
(invoke-hooks-for-imported-graph conn tx-report)
|
||||
|
||||
:else
|
||||
(invoke-hooks-default repo conn tx-report context)))))
|
||||
:else
|
||||
(invoke-hooks-default repo conn tx-report context))))
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[frontend.worker.rtc.ws-util :as ws-util]
|
||||
[frontend.worker.state :as worker-state]
|
||||
[logseq.common.path :as path]
|
||||
[logseq.db :as ldb]
|
||||
[malli.core :as ma]
|
||||
[missionary.core :as m]))
|
||||
|
||||
@@ -144,11 +145,11 @@
|
||||
(throw (ex-info "upload asset failed" r)))
|
||||
;; asset might be deleted by the user before uploaded successfully
|
||||
(when (d/entity @conn [:block/uuid asset-uuid])
|
||||
(d/transact! conn
|
||||
[{:block/uuid asset-uuid
|
||||
:logseq.property.asset/remote-metadata {:checksum checksum :type asset-type}}]
|
||||
(ldb/transact! conn
|
||||
[{:block/uuid asset-uuid
|
||||
:logseq.property.asset/remote-metadata {:checksum checksum :type asset-type}}]
|
||||
;; Don't generate rtc ops again, (block-ops & asset-ops)
|
||||
{:persist-op? false}))
|
||||
{:persist-op? false}))
|
||||
(client-op/remove-asset-op repo asset-uuid))))
|
||||
(c.m/concurrent-exec-flow 3 (m/seed asset-uuid->url))
|
||||
(m/reduce (constantly nil))))
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[frontend.worker.rtc.malli-schema :as rtc-schema]
|
||||
[frontend.worker.state :as worker-state]
|
||||
[lambdaisland.glogi :as log]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.sqlite.util :as sqlite-util]
|
||||
[malli.core :as ma]
|
||||
[malli.transform :as mt]
|
||||
@@ -101,7 +102,7 @@
|
||||
[repo graph-uuid]
|
||||
{:pre [(some? graph-uuid)]}
|
||||
(when-let [conn (worker-state/get-client-ops-conn repo)]
|
||||
(d/transact! conn [[:db/add "e" :graph-uuid graph-uuid]])))
|
||||
(ldb/transact! conn [[:db/add "e" :graph-uuid graph-uuid]])))
|
||||
|
||||
(defn get-graph-uuid
|
||||
[repo]
|
||||
@@ -117,13 +118,13 @@
|
||||
(if-let [datom (first (d/datoms @conn :avet :local-tx))]
|
||||
[:db/add (:e datom) :local-tx t]
|
||||
[:db/add "e" :local-tx t])]
|
||||
(d/transact! conn [tx-data]))))
|
||||
(ldb/transact! conn [tx-data]))))
|
||||
|
||||
(defn remove-local-tx
|
||||
[repo]
|
||||
(when-let [conn (worker-state/get-client-ops-conn repo)]
|
||||
(when-let [datom (first (d/datoms @conn :avet :local-tx))]
|
||||
(d/transact! conn [[:db/retract (:e datom) :local-tx]]))))
|
||||
(ldb/transact! conn [[:db/retract (:e datom) :local-tx]]))))
|
||||
|
||||
(defn get-local-tx
|
||||
[repo]
|
||||
@@ -291,7 +292,7 @@
|
||||
tx-data2 (when (seq update-kv-value-ops) (generate-ident-kv-ops-tx-data @conn update-kv-value-ops))
|
||||
tx-data3 (when (seq rename-db-ident-ops) (generate-rename-db-ident-ops-tx-data rename-db-ident-ops))]
|
||||
(when-let [tx-data (not-empty (concat tx-data1 tx-data2 tx-data3))]
|
||||
(d/transact! conn tx-data)))))
|
||||
(ldb/transact! conn tx-data)))))
|
||||
|
||||
(defn- get-all-block-ops*
|
||||
"Return e->op-map"
|
||||
@@ -352,7 +353,7 @@
|
||||
(let [e->op-map (get-all-block-ops* @conn)
|
||||
retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) block-op-types))
|
||||
(keys e->op-map))]
|
||||
(d/transact! conn retract-all-tx-data)
|
||||
(ldb/transact! conn retract-all-tx-data)
|
||||
(vals e->op-map)))
|
||||
|
||||
(defn- get&remove-all-update-kv-value-ops*
|
||||
@@ -360,7 +361,7 @@
|
||||
(let [e->op-map (get-all-update-kv-value-ops* @conn)
|
||||
retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) update-kv-value-op-types))
|
||||
(keys e->op-map))]
|
||||
(d/transact! conn retract-all-tx-data)
|
||||
(ldb/transact! conn retract-all-tx-data)
|
||||
(vals e->op-map)))
|
||||
|
||||
(defn- get&remove-all-rename-db-ident-ops*
|
||||
@@ -368,7 +369,7 @@
|
||||
(let [e->op-map (get-all-rename-db-ident-ops* @conn)
|
||||
retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) db-ident-rename-op-types))
|
||||
(keys e->op-map))]
|
||||
(d/transact! conn retract-all-tx-data)
|
||||
(ldb/transact! conn retract-all-tx-data)
|
||||
(vals e->op-map)))
|
||||
|
||||
(defn get-all-block-ops
|
||||
@@ -468,7 +469,7 @@
|
||||
(cond-> [{:block/uuid block-uuid
|
||||
:remove-asset op}]
|
||||
update-asset-op (conj [:db.fn/retractAttribute e :update-asset]))))))]
|
||||
(d/transact! conn tx-data)))))))
|
||||
(ldb/transact! conn tx-data)))))))
|
||||
|
||||
(defn add-all-exists-asset-as-ops
|
||||
[repo]
|
||||
@@ -516,7 +517,7 @@
|
||||
(when-let [conn (worker-state/get-client-ops-conn repo)]
|
||||
(let [ent (d/entity @conn [:block/uuid asset-uuid])]
|
||||
(when-let [e (:db/id ent)]
|
||||
(d/transact! conn (map (fn [a] [:db.fn/retractAttribute e a]) asset-op-types))))))
|
||||
(ldb/transact! conn (map (fn [a] [:db.fn/retractAttribute e a]) asset-op-types))))))
|
||||
|
||||
(defn create-pending-asset-ops-count-flow
|
||||
[repo]
|
||||
|
||||
@@ -156,9 +156,9 @@
|
||||
(defn- update-remote-schema-version!
|
||||
[conn server-schema-version]
|
||||
(when server-schema-version
|
||||
(d/transact! conn [(ldb/kv :logseq.kv/remote-schema-version server-schema-version)]
|
||||
{:gen-undo-ops? false
|
||||
:persist-op? false})))
|
||||
(ldb/transact! conn [(ldb/kv :logseq.kv/remote-schema-version server-schema-version)]
|
||||
{:gen-undo-ops? false
|
||||
:persist-op? false})))
|
||||
|
||||
(defonce ^:private *rtc-lock (atom nil))
|
||||
(defn- holding-rtc-lock
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
(ns frontend.worker.rtc.db
|
||||
"rtc db ops"
|
||||
(:require [datascript.core :as d]
|
||||
[frontend.worker.state :as worker-state]))
|
||||
[frontend.worker.state :as worker-state]
|
||||
[logseq.db :as ldb]))
|
||||
|
||||
(defn remove-rtc-data-from-local-db!
|
||||
[repo]
|
||||
(when-let [conn (worker-state/get-datascript-conn repo)]
|
||||
(d/transact! conn [[:db/retractEntity :logseq.kv/graph-uuid]
|
||||
[:db/retractEntity :logseq.kv/graph-local-tx]
|
||||
[:db/retractEntity :logseq.kv/remote-schema-version]])))
|
||||
(ldb/transact! conn [[:db/retractEntity :logseq.kv/graph-uuid]
|
||||
[:db/retractEntity :logseq.kv/graph-local-tx]
|
||||
[:db/retractEntity :logseq.kv/remote-schema-version]])))
|
||||
|
||||
(defn reset-client-op-conn
|
||||
[repo]
|
||||
@@ -18,7 +19,7 @@
|
||||
(d/datoms @conn :avet :aes-key-jwk)
|
||||
(d/datoms @conn :avet :block/uuid))
|
||||
(map (fn [datom] [:db/retractEntity (:e datom)])))]
|
||||
(d/transact! conn tx-data))))
|
||||
(ldb/transact! conn tx-data))))
|
||||
|
||||
(defn remove-rtc-data-in-conn!
|
||||
[repo]
|
||||
|
||||
@@ -215,11 +215,11 @@
|
||||
(when-let [conn (worker-state/get-datascript-conn repo)]
|
||||
(let [db @conn]
|
||||
(when-let [schema-version (:kv/value (d/entity db :logseq.kv/schema-version))]
|
||||
(d/transact! conn
|
||||
[(ldb/kv :logseq.kv/remote-schema-version schema-version)]
|
||||
{:rtc-download-graph? true
|
||||
:gen-undo-ops? false
|
||||
:persist-op? false})))))
|
||||
(ldb/transact! conn
|
||||
[(ldb/kv :logseq.kv/remote-schema-version schema-version)]
|
||||
{:rtc-download-graph? true
|
||||
:gen-undo-ops? false
|
||||
:persist-op? false})))))
|
||||
|
||||
(defn- <transact-block-refs!
|
||||
[repo graph-uuid]
|
||||
@@ -376,7 +376,7 @@
|
||||
{:rtc-download-graph? true
|
||||
:gen-undo-ops? false
|
||||
;; only transact db schema, skip validation to avoid warning
|
||||
:frontend.worker.pipeline/skip-validate-db? true
|
||||
:skip-validate-db? true
|
||||
:persist-op? false}
|
||||
(worker-state/get-context))
|
||||
(rtc-log-and-state/rtc-log :rtc.log/download {:sub-type :transact-graph-data-to-db-2
|
||||
@@ -534,7 +534,7 @@
|
||||
{:rtc-download-graph? true
|
||||
:gen-undo-ops? false
|
||||
;; only transact db schema, skip validation to avoid warning
|
||||
:frontend.worker.pipeline/skip-validate-db? true
|
||||
:skip-validate-db? true
|
||||
:persist-op? false}
|
||||
(worker-state/get-context))
|
||||
(prn :xxx3 (js/Date.))
|
||||
|
||||
@@ -208,13 +208,14 @@ so need to pull earlier remote-data from websocket."})
|
||||
b (d/entity @conn [:block/uuid block-uuid])]
|
||||
(case [whiteboard-page-block? (some? local-parent) (some? remote-block-order)]
|
||||
[false true true]
|
||||
(do (if move?
|
||||
(transact-db! :move-blocks repo conn [(block-reuse-db-id b)] local-parent {:sibling? false})
|
||||
(transact-db! :insert-blocks repo conn
|
||||
[{:block/uuid block-uuid
|
||||
:block/title ""}]
|
||||
local-parent {:sibling? false :keep-uuid? true}))
|
||||
(transact-db! :update-block-order-directly repo conn block-uuid first-remote-parent remote-block-order))
|
||||
(do
|
||||
(if move?
|
||||
(transact-db! :move-blocks repo conn [(block-reuse-db-id b)] local-parent {:sibling? false})
|
||||
(transact-db! :insert-blocks repo conn
|
||||
[{:block/uuid block-uuid
|
||||
:block/title ""}]
|
||||
local-parent {:sibling? false :keep-uuid? true}))
|
||||
(transact-db! :update-block-order-directly repo conn block-uuid first-remote-parent remote-block-order))
|
||||
|
||||
[false true false]
|
||||
(if move?
|
||||
@@ -447,7 +448,8 @@ so need to pull earlier remote-data from websocket."})
|
||||
remote-v* (set (map ldb/read-transit-str remote-v))
|
||||
[local-only remote-only] (data/diff (set local-v) remote-v*)]
|
||||
(cond-> []
|
||||
(seq local-only) (concat (map (fn [v] [:db/retract e k v]) local-only))
|
||||
(seq local-only) (concat (map (fn [v]
|
||||
[:db/retract e k v]) local-only))
|
||||
(seq remote-only) (concat (map (fn [v] [:db/add e k v]) remote-only)))))))
|
||||
|
||||
(defn- diff-block-map->tx-data
|
||||
@@ -564,15 +566,17 @@ so need to pull earlier remote-data from websocket."})
|
||||
(doseq [{:keys [self _page-name]
|
||||
title :block/title
|
||||
:as op-value} update-page-ops]
|
||||
(let [create-opts {:uuid self
|
||||
:old-db-id (@worker-state/*deleted-block-uuid->db-id self)}
|
||||
[_ page-name page-uuid] (worker-page/rtc-create-page! conn config
|
||||
(ldb/read-transit-str title)
|
||||
create-opts)]
|
||||
;; TODO: current page-create fn is buggy, even provide :uuid option, it will create-page with different uuid,
|
||||
;; if there's already existing same name page
|
||||
(assert (= page-uuid self) {:page-name page-name :page-uuid page-uuid :should-be self})
|
||||
(assert (some? (d/entity @conn [:block/uuid page-uuid])) {:page-uuid page-uuid :page-name page-name})
|
||||
(let [db-ident (:db/ident op-value)]
|
||||
(when-not (and db-ident (d/entity @conn db-ident)) ; property or class exists
|
||||
(let [create-opts {:uuid self
|
||||
:old-db-id (@worker-state/*deleted-block-uuid->db-id self)}
|
||||
[_ page-name page-uuid] (worker-page/rtc-create-page! conn config
|
||||
(ldb/read-transit-str title)
|
||||
create-opts)]
|
||||
;; TODO: current page-create fn is buggy, even provide :uuid option, it will create-page with different uuid,
|
||||
;; if there's already existing same name page
|
||||
(assert (= page-uuid self) {:page-name page-name :page-uuid page-uuid :should-be self})
|
||||
(assert (some? (d/entity @conn [:block/uuid page-uuid])) {:page-uuid page-uuid :page-name page-name})))
|
||||
(update-block-attrs repo conn self op-value)))))
|
||||
|
||||
(defn- ensure-refed-blocks-exist
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
182
src/main/logseq/api/app.cljs
Normal file
182
src/main/logseq/api/app.cljs
Normal file
@@ -0,0 +1,182 @@
|
||||
(ns logseq.api.app
|
||||
"app state/ui related apis"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[clojure.string :as string]
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.config :as config]
|
||||
[frontend.db.utils :as db-utils]
|
||||
[frontend.handler.command-palette :as palette-handler]
|
||||
[frontend.handler.config :as config-handler]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.handler.recent :as recent-handler]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[frontend.version :as fv]
|
||||
[logseq.api.db-based :as db-based-api]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[reitit.frontend.easy :as rfe]))
|
||||
|
||||
(defn get_state_from_store
|
||||
[^js path]
|
||||
(when-let [path (if (string? path) [path] (bean/->clj path))]
|
||||
(some->> path
|
||||
(map #(if (string/starts-with? % "@")
|
||||
(subs % 1)
|
||||
(keyword %)))
|
||||
(get-in @state/state)
|
||||
(#(if (util/atom? %) @% %))
|
||||
(sdk-utils/normalize-keyword-for-json)
|
||||
(bean/->js))))
|
||||
|
||||
(defn set_state_from_store
|
||||
[^js path ^js value]
|
||||
(when-let [path (if (string? path) [path] (bean/->clj path))]
|
||||
(some->> path
|
||||
(map #(if (string/starts-with? % "@")
|
||||
(subs % 1)
|
||||
(keyword %)))
|
||||
(into [])
|
||||
(#(state/set-state! % (bean/->clj value))))))
|
||||
|
||||
(defn get_app_info
|
||||
;; get app base info
|
||||
[]
|
||||
(-> (sdk-utils/normalize-keyword-for-json
|
||||
{:version fv/version
|
||||
:supportDb true})
|
||||
(bean/->js)))
|
||||
|
||||
(def get_user_configs
|
||||
(fn []
|
||||
(bean/->js
|
||||
(sdk-utils/normalize-keyword-for-json
|
||||
{:preferred-language (:preferred-language @state/state)
|
||||
:preferred-theme-mode (:ui/theme @state/state)
|
||||
:preferred-format (state/get-preferred-format)
|
||||
:preferred-workflow (state/get-preferred-workflow)
|
||||
:preferred-todo (state/get-preferred-todo)
|
||||
:preferred-date-format (state/get-date-formatter)
|
||||
:preferred-start-of-week (state/get-start-of-week)
|
||||
:current-graph (state/get-current-repo)
|
||||
:show-brackets (state/show-brackets?)
|
||||
:enabled-journals (state/enable-journals?)
|
||||
:enabled-flashcards (state/enable-flashcards?)
|
||||
:me (state/get-me)}))))
|
||||
|
||||
(def get_current_graph_configs
|
||||
(fn [& keys]
|
||||
(some-> (state/get-config)
|
||||
(#(if (seq keys) (get-in % (map keyword keys)) %))
|
||||
(bean/->js))))
|
||||
|
||||
(def set_current_graph_configs
|
||||
(fn [^js configs]
|
||||
(when-let [configs (bean/->clj configs)]
|
||||
(when (map? configs)
|
||||
(doseq [[k v] configs]
|
||||
(config-handler/set-config! k v))))))
|
||||
|
||||
(def get_current_graph_favorites
|
||||
(fn []
|
||||
(if (config/db-based-graph?)
|
||||
(db-based-api/get-favorites)
|
||||
(some->> (:favorites (state/get-config))
|
||||
(remove string/blank?)
|
||||
(filter string?)
|
||||
(bean/->js)))))
|
||||
|
||||
(def get_current_graph_recent
|
||||
(fn []
|
||||
(some->> (recent-handler/get-recent-pages)
|
||||
(map #(db-utils/entity (:db/id %)))
|
||||
(remove nil?)
|
||||
(sdk-utils/normalize-keyword-for-json)
|
||||
(bean/->js))))
|
||||
|
||||
(def get_current_graph
|
||||
(fn []
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-not (= config/demo-repo repo)
|
||||
(bean/->js {:url repo
|
||||
:name (util/node-path.basename repo)
|
||||
:path (config/get-repo-dir repo)})))))
|
||||
|
||||
(def show_themes
|
||||
(fn []
|
||||
(state/pub-event! [:modal/show-themes-modal])))
|
||||
|
||||
(def set_theme_mode
|
||||
(fn [mode]
|
||||
(state/set-theme-mode! mode)))
|
||||
|
||||
(def relaunch
|
||||
(fn []
|
||||
(ipc/ipc "relaunchApp")))
|
||||
|
||||
(def quit
|
||||
(fn []
|
||||
(ipc/ipc "quitApp")))
|
||||
|
||||
(def open_external_link
|
||||
(fn [url]
|
||||
(when (re-find #"https?://" url)
|
||||
(js/apis.openExternal url))))
|
||||
|
||||
(def invoke_external_command
|
||||
(fn [type & args]
|
||||
(when-let [id (and (string/starts-with? type "logseq.")
|
||||
(-> (string/replace type #"^logseq." "")
|
||||
(util/safe-lower-case)
|
||||
(keyword)))]
|
||||
(when-let [action (get-in (palette-handler/get-commands-unique) [id :action])]
|
||||
(apply plugin-handler/hook-lifecycle-fn! id action args)))))
|
||||
|
||||
;; flag - boolean | 'toggle'
|
||||
(def set_left_sidebar_visible
|
||||
(fn [flag]
|
||||
(if (= flag "toggle")
|
||||
(state/toggle-left-sidebar!)
|
||||
(state/set-state! :ui/left-sidebar-open? (boolean flag)))
|
||||
nil))
|
||||
|
||||
;; flag - boolean | 'toggle'
|
||||
(def set_right_sidebar_visible
|
||||
(fn [flag]
|
||||
(if (= flag "toggle")
|
||||
(state/toggle-sidebar-open?!)
|
||||
(state/set-state! :ui/sidebar-open? (boolean flag)))
|
||||
nil))
|
||||
|
||||
(def clear_right_sidebar_blocks
|
||||
(fn [^js opts]
|
||||
(state/clear-sidebar-blocks!)
|
||||
(when-let [opts (and opts (bean/->clj opts))]
|
||||
(and (:close opts) (state/hide-right-sidebar!)))
|
||||
nil))
|
||||
|
||||
(def push_state
|
||||
(fn [^js k ^js params ^js query]
|
||||
(let [k (keyword k)
|
||||
page? (= k :page)
|
||||
params (bean/->clj params)
|
||||
query (bean/->clj query)]
|
||||
(if page?
|
||||
(-> (:name params)
|
||||
(route-handler/redirect-to-page! {:anchor (:anchor query) :push true}))
|
||||
(rfe/push-state k params query)))))
|
||||
|
||||
(def replace_state
|
||||
(fn [^js k ^js params ^js query]
|
||||
(let [k (keyword k)
|
||||
page? (= k :page)
|
||||
params (bean/->clj params)
|
||||
query (bean/->clj query)]
|
||||
(if-let [page-name (and page? (:name params))]
|
||||
(route-handler/redirect-to-page! page-name {:anchor (:anchor query) :push false})
|
||||
(rfe/replace-state k params query)))))
|
||||
@@ -2,30 +2,21 @@
|
||||
"Block related apis"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[frontend.config :as config]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.async :as db-async]
|
||||
[frontend.db.conn :as conn]
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.db.utils :as db-utils]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.db-based.property.util :as db-pu]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.handler.property :as property-handler]
|
||||
[frontend.modules.outliner.op :as outliner-op]
|
||||
[frontend.modules.outliner.tree :as outliner-tree]
|
||||
[frontend.modules.outliner.ui :as ui-outliner-tx]
|
||||
[frontend.state :as state]
|
||||
[logseq.db :as ldb]
|
||||
[frontend.util :as util]
|
||||
[logseq.db.frontend.db-ident :as db-ident]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[promesa.core :as p]))
|
||||
[logseq.db.frontend.property.type :as db-property-type]
|
||||
[logseq.sdk.utils :as sdk-utils]))
|
||||
|
||||
(defn convert?to-built-in-property-name
|
||||
[property-name]
|
||||
(if (and (not (qualified-keyword? property-name))
|
||||
(contains? #{:background-color} property-name))
|
||||
(keyword :logseq.property property-name)
|
||||
property-name))
|
||||
(def plugin-property-prefix "plugin.property.")
|
||||
|
||||
(defn sanitize-user-property-name
|
||||
[k]
|
||||
@@ -36,33 +27,43 @@
|
||||
(#(cond-> %
|
||||
(not (string/includes? % "/"))
|
||||
(string/lower-case))))
|
||||
k))
|
||||
(str k)))
|
||||
|
||||
(defn get-sanitized-plugin-id
|
||||
[^js plugin]
|
||||
(or
|
||||
(when (some-> js/window.LSPlugin (.-PluginLocal))
|
||||
(some->> plugin (.-id) sanitize-user-property-name))
|
||||
"_test_plugin"))
|
||||
|
||||
(defn resolve-property-prefix-for-db
|
||||
[^js plugin]
|
||||
(->> (when (some-> js/window.LSPlugin (.-PluginLocal))
|
||||
(or (some->> plugin (.-id) (sanitize-user-property-name) (str "."))
|
||||
"._api"))
|
||||
(str "plugin.property")))
|
||||
(let [plugin-id (get-sanitized-plugin-id plugin)]
|
||||
(when-not plugin-id
|
||||
(js/console.error "Can't get current plugin id")
|
||||
(throw (ex-info "Can't get current plugin id"
|
||||
{:plugin plugin})))
|
||||
(str plugin-property-prefix plugin-id)))
|
||||
|
||||
;; FIXME: This ns should not be creating idents. This allows for ident conflicts
|
||||
;; and assumes that names directly map to idents which is incorrect and breaks for multiple
|
||||
;; cases e.g. a property that has been renamed or sanitized. Instead it should
|
||||
;; find a property's ident by looking up the property in the db by its title
|
||||
(defn get-db-ident-for-user-property-name
|
||||
(defn get-db-ident-from-property-name
|
||||
"Finds a property :db/ident for a given property name"
|
||||
([property-name] (get-db-ident-for-user-property-name property-name "user.property"))
|
||||
([property-name prefix]
|
||||
(let [property-name' (if (string? property-name)
|
||||
(keyword property-name) property-name)
|
||||
property-name' (convert?to-built-in-property-name property-name')]
|
||||
(if (qualified-keyword? property-name') property-name'
|
||||
(db-ident/create-db-ident-from-name prefix (name property-name) false)))))
|
||||
[property-name plugin]
|
||||
(let [property-name' (->
|
||||
(if-not (string? property-name)
|
||||
(str property-name)
|
||||
property-name)
|
||||
(string/replace #"^:+" ""))
|
||||
property-key (keyword property-name')]
|
||||
(if (qualified-keyword? property-key)
|
||||
property-key
|
||||
;; plugin property
|
||||
(let [plugin-ns (resolve-property-prefix-for-db plugin)]
|
||||
(keyword plugin-ns (db-ident/normalize-ident-name-part property-name'))))))
|
||||
|
||||
(defn plugin-property-key?
|
||||
[ident]
|
||||
(some-> ident (str)
|
||||
(string/starts-with? ":plugin.property.")))
|
||||
(and (qualified-keyword? ident)
|
||||
(string/starts-with? (namespace ident) plugin-property-prefix)))
|
||||
|
||||
(defn into-readable-db-properties
|
||||
[properties]
|
||||
@@ -70,114 +71,118 @@
|
||||
(db-pu/readable-properties
|
||||
{:original-key? true :key-fn str})))
|
||||
|
||||
(defn- entity->map
|
||||
[e]
|
||||
(assoc (into {} e) :db/id (:db/id e)))
|
||||
|
||||
(defn into-properties
|
||||
([block] (into-properties (state/get-current-repo) block))
|
||||
([repo block]
|
||||
(if (some-> repo (config/db-based-graph?))
|
||||
(let [e (db/entity (:db/id block))
|
||||
props (-> (:block/properties e)
|
||||
sdk-utils/remove-hidden-properties)]
|
||||
(-> (entity->map block)
|
||||
(assoc :block/properties props)))
|
||||
block)))
|
||||
|
||||
(defn parse-property-json-value-if-need
|
||||
[ident property-value]
|
||||
(when-let [prop (and (string? property-value)
|
||||
(plugin-property-key? ident)
|
||||
(some-> ident (db-utils/entity)))]
|
||||
(if (= (:logseq.property/type prop) :string)
|
||||
(if (= (:logseq.property/type prop) :json)
|
||||
(try
|
||||
(js/JSON.parse property-value)
|
||||
(catch js/Error _e
|
||||
property-value))
|
||||
property-value)))
|
||||
|
||||
(defn infer-property-value-type-to-save!
|
||||
[ident value]
|
||||
(let [as-json? (coll? value)
|
||||
value-handle
|
||||
(fn [type multi?]
|
||||
(let [as-json? (or (= type :string) as-json?)]
|
||||
(if multi?
|
||||
(-> (for [v value]
|
||||
(when-let [page (some-> v (str) (string/trim))]
|
||||
(let [id (:db/id (ldb/get-case-page (conn/get-db) page))]
|
||||
(if (nil? id)
|
||||
(-> (page-handler/<create! page {:redirect? false})
|
||||
(p/then #(:db/id %)))
|
||||
id))))
|
||||
(p/all)
|
||||
(p/then (fn [vs] [ident :logseq.property/empty-placeholder vs true])))
|
||||
(let [value (if as-json? (js/JSON.stringify (bean/->js value)) value)]
|
||||
[ident value nil false]))))
|
||||
ent (db-utils/entity ident)]
|
||||
(if (not ent)
|
||||
(let [type (cond
|
||||
(boolean? value) :checkbox
|
||||
(number? value) :number
|
||||
(coll? value) :string
|
||||
:else :default)
|
||||
schema {:logseq.property/type type
|
||||
:db/cardinality :one}]
|
||||
(p/chain
|
||||
(db-property-handler/upsert-property! ident schema {})
|
||||
(fn [] (value-handle type false))))
|
||||
(let [value-multi? (vector? value)
|
||||
ident (:db/ident ent)
|
||||
ent-type (:logseq.property/type ent)
|
||||
ent-type-str? (= ent-type :string)
|
||||
ent-multi? (= (:db/cardinality ent) :db.cardinality/many)
|
||||
cardinality-want-illegal-changed? (and (not value-multi?) ent-multi?)]
|
||||
(when cardinality-want-illegal-changed?
|
||||
(throw (js/Error. "Multiple property type can not be changed.")))
|
||||
(p/chain
|
||||
(db-property-handler/upsert-property! ident
|
||||
{:logseq.property/type ent-type
|
||||
:db/cardinality (if (and (not ent-type-str?) value-multi?) :many :one)}
|
||||
{})
|
||||
#(value-handle ent-type ent-multi?))))))
|
||||
(defn ensure-property-upsert-control
|
||||
"Plugin should only upsert its own properties"
|
||||
[plugin property-ident property-name]
|
||||
(when-not (= (resolve-property-prefix-for-db plugin)
|
||||
(namespace property-ident))
|
||||
(throw (ex-info "Plugins can only upsert its own properties"
|
||||
{:property property-name
|
||||
:property-ident property-ident}))))
|
||||
|
||||
(defn save-db-based-block-properties!
|
||||
([block properties] (save-db-based-block-properties! block properties nil))
|
||||
([block properties ^js plugin]
|
||||
(when-let [block-id (and (seq properties) (:db/id block))]
|
||||
(let [properties (update-keys properties
|
||||
(fn [k]
|
||||
(let [prefix (resolve-property-prefix-for-db plugin)]
|
||||
(get-db-ident-for-user-property-name k prefix))))
|
||||
*properties-page-refs (volatile! {})]
|
||||
(-> (for [ident (keys properties)]
|
||||
(p/let [ret (infer-property-value-type-to-save! ident (get properties ident))]
|
||||
ret))
|
||||
(p/all)
|
||||
(p/chain
|
||||
(fn [props]
|
||||
(->> props
|
||||
(reduce (fn [a [k v vs multi?]]
|
||||
(if multi?
|
||||
(do (vswap! *properties-page-refs assoc k vs) a)
|
||||
(assoc a k v))) {})
|
||||
(db-property-handler/set-block-properties! block-id)))
|
||||
;; handle page refs
|
||||
(fn []
|
||||
(when (seq @*properties-page-refs)
|
||||
(doseq [[ident refs] @*properties-page-refs]
|
||||
(-> (property-handler/remove-block-property! (state/get-current-repo) block-id ident)
|
||||
(p/then
|
||||
(fn []
|
||||
(if (seq refs)
|
||||
(ui-outliner-tx/transact!
|
||||
{:outliner-op :set-block-properties}
|
||||
(doseq [eid refs]
|
||||
(when (number? eid)
|
||||
(property-handler/set-block-property!
|
||||
(state/get-current-repo) block-id ident eid))))
|
||||
(db-property-handler/set-block-property! block-id ident :logseq.property/empty-placeholder))))))))))))))
|
||||
(defn- convert-json-and-string
|
||||
[property-type value]
|
||||
(cond
|
||||
(and (= :json property-type) (not (string? value)))
|
||||
(js/JSON.stringify (bean/->js value))
|
||||
|
||||
(and (= :string property-type) (not (string? value)))
|
||||
(str value)
|
||||
|
||||
:else
|
||||
value))
|
||||
|
||||
(defn- infer-property-type
|
||||
[value]
|
||||
(cond
|
||||
(boolean? value) :checkbox
|
||||
(or (number? value)
|
||||
(and (sequential? value) (every? number? value))) :number
|
||||
(or (db-property-type/url? value)
|
||||
(and (sequential? value) (every? db-property-type/url? value))) :url
|
||||
(map? value) :json
|
||||
:else :default))
|
||||
|
||||
(defn- set-block-properties!
|
||||
[plugin block-id properties]
|
||||
(ui-outliner-tx/transact!
|
||||
{:outliner-op :set-block-properties}
|
||||
(doseq [[property-id property-ident value schema] properties]
|
||||
(when-not (qualified-keyword? property-ident)
|
||||
(js/console.error (str "Invalid property id: " property-id))
|
||||
(throw (ex-info "Invalid property id" {:property-id property-id
|
||||
:property-ident property-ident})))
|
||||
(let [property (db/entity property-ident)
|
||||
property-type (or (:logseq.property/type property)
|
||||
(some-> (:type schema) keyword)
|
||||
(and (nil? property)
|
||||
(infer-property-type value)))
|
||||
cardinality (or (:db/cardinality property)
|
||||
(if (and (or (= "many" (:cardinality schema))
|
||||
(sequential? value))
|
||||
(not= (:cardinality schema) "one"))
|
||||
:db.cardinality/many
|
||||
:db.cardinality/one))
|
||||
_ (when (and (= cardinality :many) (= property-type :json))
|
||||
(throw (ex-info ":json type doesn't support multiple values" {:property-id property-ident})))
|
||||
error-data {:property-id property-id
|
||||
:property-ident property-ident
|
||||
:schema schema
|
||||
:value value}
|
||||
schema' {:logseq.property/type property-type
|
||||
:db/cardinality cardinality}
|
||||
many? (= cardinality :db.cardinality/many)
|
||||
value' (if (and many? (not (sequential? value)))
|
||||
(when value [value])
|
||||
value)]
|
||||
|
||||
(when (and property schema)
|
||||
(throw (ex-info "Use `upsert_property` to modify existing property's schema"
|
||||
error-data)))
|
||||
|
||||
(when-not property-type
|
||||
(throw (ex-info (str "Missing `type` in schema for property: " property-id)
|
||||
error-data)))
|
||||
|
||||
(when (and (not many?) (sequential? value))
|
||||
(throw (ex-info (util/format "Property %s has cardinality `one` but passed multiple values" property-id)
|
||||
error-data)))
|
||||
|
||||
(when-not property
|
||||
(ensure-property-upsert-control plugin property-ident property-id)
|
||||
(outliner-op/upsert-property! property-ident schema' {:property-name property-id}))
|
||||
|
||||
(when (and property (or many? (nil? value'))) ; delete property from this block
|
||||
(outliner-op/remove-block-property! block-id property-ident))
|
||||
|
||||
(let [set-property! (fn [value]
|
||||
(outliner-op/set-block-property! block-id property-ident
|
||||
(convert-json-and-string property-type value)))
|
||||
values (if (sequential? value') value' [value'])]
|
||||
(doseq [value values]
|
||||
(set-property! value)))))))
|
||||
|
||||
(defn db-based-save-block-properties!
|
||||
[block properties & {:keys [plugin schema]}]
|
||||
(when-let [block-id (and (seq properties) (:db/id block))]
|
||||
(let [properties (->> properties
|
||||
(map (fn [[k v]]
|
||||
(let [ident (get-db-ident-from-property-name k plugin)
|
||||
property-schema (get schema k)]
|
||||
[k ident v property-schema]))))]
|
||||
(set-block-properties! plugin block-id properties))))
|
||||
|
||||
(defn <sync-children-blocks!
|
||||
[block]
|
||||
@@ -204,6 +209,5 @@
|
||||
;; attached shallow children
|
||||
(assoc block :block/children
|
||||
(map #(list :uuid (:block/uuid %))
|
||||
(db/get-block-immediate-children repo uuid))))
|
||||
block (into-properties repo block)]
|
||||
(db/get-block-immediate-children repo uuid))))]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json block)))))))
|
||||
|
||||
51
src/main/logseq/api/db.cljs
Normal file
51
src/main/logseq/api/db.cljs
Normal file
@@ -0,0 +1,51 @@
|
||||
(ns logseq.api.db
|
||||
"DB APIs"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.async :as db-async]
|
||||
[frontend.db.query-custom :as query-custom]
|
||||
[frontend.db.query-dsl :as query-dsl]
|
||||
[frontend.db.query-react :as query-react]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.state :as state]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn q
|
||||
[query-string]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [result (query-dsl/query repo query-string
|
||||
{:disable-reactive? true
|
||||
:return-promise? true})]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json (flatten result))))))
|
||||
|
||||
(defn datascript_query
|
||||
[query & inputs]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [db (db/get-db repo)]
|
||||
(p/let [query (cljs.reader/read-string query)
|
||||
resolved-inputs (map #(cond
|
||||
(string? %)
|
||||
(some->> % (cljs.reader/read-string) (query-react/resolve-input db))
|
||||
|
||||
(fn? %)
|
||||
(fn [& args]
|
||||
(.apply % nil (clj->js (mapv bean/->js args))))
|
||||
|
||||
:else %)
|
||||
inputs)
|
||||
result (apply db-async/<q repo {:transact-db? false}
|
||||
(cons query resolved-inputs))]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json result false))))))
|
||||
|
||||
(defn custom_query
|
||||
[query-string]
|
||||
(p/let [result (let [query (cljs.reader/read-string query-string)]
|
||||
(query-custom/custom-query {:query query
|
||||
:disable-reactive? true
|
||||
:return-promise? true}))]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json (flatten result)))))
|
||||
194
src/main/logseq/api/db_based.cljs
Normal file
194
src/main/logseq/api/db_based.cljs
Normal file
@@ -0,0 +1,194 @@
|
||||
(ns logseq.api.db-based
|
||||
"DB version related fns"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as walk]
|
||||
[datascript.core :as d]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.handler.common.page :as page-common-handler]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[logseq.api.block :as api-block]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn -get-property
|
||||
[^js plugin k]
|
||||
(when-let [k' (and (string? k) (api-block/sanitize-user-property-name k))]
|
||||
(let [property-ident (api-block/get-db-ident-from-property-name k' plugin)]
|
||||
(db/entity property-ident))))
|
||||
|
||||
(defn get-favorites
|
||||
[]
|
||||
(p/let [favorites (page-handler/get-favorites)]
|
||||
(sdk-utils/result->js favorites)))
|
||||
|
||||
(defn insert-batch-blocks
|
||||
[this target blocks opts]
|
||||
(let [blocks' (walk/prewalk
|
||||
(fn [f]
|
||||
(if (and (map? f) (:content f) (nil? (:uuid f)))
|
||||
(assoc f :uuid (d/squuid))
|
||||
f))
|
||||
blocks)
|
||||
{:keys [sibling before schema]} opts
|
||||
block (if before
|
||||
(db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id target))))) target)
|
||||
sibling? (if (ldb/page? block) false sibling)
|
||||
uuid->properties (let [blocks (outliner-core/tree-vec-flatten blocks' :children)]
|
||||
(when (some (fn [b] (seq (:properties b))) blocks)
|
||||
(zipmap (map :uuid blocks)
|
||||
(map :properties blocks))))]
|
||||
(p/let [result (editor-handler/insert-block-tree-after-target
|
||||
(:db/id block) sibling? blocks' (get block :block/format :markdown) true)
|
||||
blocks (:blocks result)]
|
||||
(when (seq blocks)
|
||||
(p/doseq [block blocks]
|
||||
(let [id (:block/uuid block)
|
||||
b (db/entity [:block/uuid id])
|
||||
properties (when uuid->properties (uuid->properties id))]
|
||||
(when (seq properties)
|
||||
(api-block/db-based-save-block-properties! b properties {:plugin this
|
||||
:schema schema})))))
|
||||
(let [blocks' (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)]
|
||||
(sdk-utils/result->js blocks')))))
|
||||
|
||||
(defn insert-block
|
||||
[this content properties schema opts]
|
||||
(p/let [new-block (editor-handler/api-insert-new-block! content opts)]
|
||||
(when (seq properties)
|
||||
(api-block/db-based-save-block-properties! new-block properties {:plugin this
|
||||
:schema schema}))
|
||||
(let [block (db/entity [:block/uuid (:block/uuid new-block)])]
|
||||
(sdk-utils/result->js block))))
|
||||
|
||||
(defn update-block
|
||||
[this block content opts]
|
||||
(when block
|
||||
(let [repo (state/get-current-repo)
|
||||
block-uuid (:block/uuid block)]
|
||||
(p/do!
|
||||
(when (seq (:properties opts))
|
||||
(api-block/db-based-save-block-properties! block (:properties opts)
|
||||
{:plugin this
|
||||
:schema (:schema opts)}))
|
||||
(editor-handler/save-block! repo
|
||||
(sdk-utils/uuid-or-throw-error block-uuid) content
|
||||
(dissoc opts :properties))))))
|
||||
|
||||
(defn get-property
|
||||
[k]
|
||||
(this-as this
|
||||
(p/let [prop (-get-property this k)]
|
||||
(some-> prop
|
||||
(assoc :type (:logseq.property/type prop))
|
||||
(sdk-utils/normalize-keyword-for-json)
|
||||
(bean/->js)))))
|
||||
|
||||
(defn ->cardinality
|
||||
[input]
|
||||
(let [valid-input #{"one" "many" "db.cardinality/one" "db.cardinality/many"}]
|
||||
(when-not (contains? valid-input input)
|
||||
(throw (ex-info "Invalid cardinality, choices: \"one\" or \"many\"" {:input input})))
|
||||
(let [result (keyword input)]
|
||||
(case result
|
||||
:one :db.cardinality/one
|
||||
:many :db.cardinality/many
|
||||
result))))
|
||||
|
||||
(defn- schema-type-check!
|
||||
[type]
|
||||
(let [valid-types #{:default :number :date :datetime :checkbox :url :node :json :string}]
|
||||
(when-not (contains? valid-types type)
|
||||
(throw (ex-info (str "Invalid type, type should be one of: " valid-types) {:type type})))))
|
||||
|
||||
(defn upsert-property
|
||||
"schema:
|
||||
{:type :default | :number | :date | :datetime | :checkbox | :url | :node | :json | :string
|
||||
:cardinality :many | :one
|
||||
:hide? true
|
||||
:view-context :page
|
||||
:public? false}
|
||||
"
|
||||
[k ^js schema ^js opts]
|
||||
(this-as
|
||||
this
|
||||
(when-not (string/blank? k)
|
||||
(p/let [opts (or (some-> opts bean/->clj) {})
|
||||
k' (api-block/sanitize-user-property-name k)
|
||||
property-ident (api-block/get-db-ident-from-property-name k' this)
|
||||
_ (api-block/ensure-property-upsert-control this property-ident k')
|
||||
schema (or (some-> schema (bean/->clj)
|
||||
(update-keys #(if (contains? #{:hide :public} %)
|
||||
(keyword (str (name %) "?")) %))) {})
|
||||
_ (when (:type schema)
|
||||
(schema-type-check! (keyword (:type schema))))
|
||||
schema (cond-> schema
|
||||
(string? (:cardinality schema))
|
||||
(-> (assoc :db/cardinality (->cardinality (:cardinality schema)))
|
||||
(dissoc :cardinality))
|
||||
|
||||
(string? (:type schema))
|
||||
(-> (assoc :logseq.property/type (keyword (:type schema)))
|
||||
(dissoc :type)))
|
||||
p (db-property-handler/upsert-property! property-ident schema
|
||||
(assoc opts :property-name k'))
|
||||
p (db/entity (:db/id p))]
|
||||
(sdk-utils/result->js p)))))
|
||||
|
||||
(defn remove-property
|
||||
[k]
|
||||
(this-as
|
||||
this
|
||||
(p/let [property (-get-property this k)]
|
||||
(when-let [uuid (and (api-block/plugin-property-key? (:db/ident property))
|
||||
(:block/uuid property))]
|
||||
(page-common-handler/<delete! uuid nil nil)))))
|
||||
|
||||
(defn upsert-block-property
|
||||
[this block key' value schema]
|
||||
(let [opts {:plugin this
|
||||
:schema (when schema
|
||||
{key schema})}]
|
||||
(api-block/db-based-save-block-properties! block {key' value} opts)))
|
||||
|
||||
(defn get-all-tags
|
||||
[]
|
||||
(-> (db-model/get-all-classes (state/get-current-repo)
|
||||
{:except-root-class? true})
|
||||
sdk-utils/result->js))
|
||||
|
||||
(defn get-all-properties
|
||||
[]
|
||||
(-> (ldb/get-all-properties (db/get-db))
|
||||
sdk-utils/result->js))
|
||||
|
||||
(defn get-tag-objects
|
||||
[class-uuid-or-ident-or-title]
|
||||
(let [eid (if (util/uuid-string? class-uuid-or-ident-or-title)
|
||||
(when-let [id (sdk-utils/uuid-or-throw-error class-uuid-or-ident-or-title)]
|
||||
[:block/uuid id])
|
||||
(let [k (keyword (api-block/sanitize-user-property-name class-uuid-or-ident-or-title))]
|
||||
(if (qualified-keyword? k)
|
||||
k
|
||||
(ldb/get-case-page (db/get-db) class-uuid-or-ident-or-title))))
|
||||
class (db/entity eid)]
|
||||
(when-not (ldb/class? class)
|
||||
(throw (ex-info "Not a tag" {:input class-uuid-or-ident-or-title})))
|
||||
(if-not class
|
||||
(throw (ex-info (str "Tag not exists with id: " eid) {}))
|
||||
(p/let [result (state/<invoke-db-worker :thread-api/get-class-objects
|
||||
(state/get-current-repo)
|
||||
(:db/id class))]
|
||||
(sdk-utils/result->js result)))))
|
||||
538
src/main/logseq/api/editor.cljs
Normal file
538
src/main/logseq/api/editor.cljs
Normal file
@@ -0,0 +1,538 @@
|
||||
(ns logseq.api.editor
|
||||
"Editor related APIs"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[clojure.string :as string]
|
||||
[frontend.commands :as commands]
|
||||
[frontend.config :as config]
|
||||
[frontend.date :as date]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.async :as db-async]
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.db.utils :as db-utils]
|
||||
[frontend.handler.code :as code-handler]
|
||||
[frontend.handler.dnd :as editor-dnd-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.export :as export-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.handler.property :as property-handler]
|
||||
[frontend.handler.shell :as shell]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.modules.outliner.tree :as outliner-tree]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[frontend.util.cursor :as cursor]
|
||||
[goog.date :as gdate]
|
||||
[goog.dom :as gdom]
|
||||
[logseq.api.block :as api-block]
|
||||
[logseq.api.db-based :as db-based-api]
|
||||
[logseq.common.util.date-time :as date-time-util]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn- <get-block
|
||||
[id-or-name & {:as opts}]
|
||||
(when id-or-name
|
||||
(db-async/<get-block (state/get-current-repo) id-or-name opts)))
|
||||
|
||||
(def save_focused_code_editor_content
|
||||
(fn []
|
||||
(code-handler/save-code-editor!)))
|
||||
|
||||
(def check_editing
|
||||
(fn []
|
||||
(if (state/get-edit-input-id)
|
||||
(str (:block/uuid (state/get-edit-block))) false)))
|
||||
|
||||
(def exit_editing_mode
|
||||
(fn [select?]
|
||||
(editor-handler/escape-editing {:select? select?})
|
||||
nil))
|
||||
|
||||
(def insert_at_editing_cursor
|
||||
(fn [content]
|
||||
(when-let [input-id (state/get-edit-input-id)]
|
||||
(commands/simple-insert! input-id content {})
|
||||
(when-let [input (gdom/getElement input-id)]
|
||||
(.focus input)))))
|
||||
|
||||
(def restore_editing_cursor
|
||||
(fn []
|
||||
(when-let [input-id (state/get-edit-input-id)]
|
||||
(when-let [input (gdom/getElement input-id)]
|
||||
(when (util/el-visible-in-viewport? input)
|
||||
(.focus input))))))
|
||||
|
||||
(def get_editing_cursor_position
|
||||
(fn []
|
||||
(when-let [input-id (state/get-edit-input-id)]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json (cursor/get-caret-pos (gdom/getElement input-id)))))))
|
||||
|
||||
(def get_editing_block_content
|
||||
(fn []
|
||||
(state/get-edit-content)))
|
||||
|
||||
(def get_selected_blocks
|
||||
(fn []
|
||||
(when-let [blocks (state/selection?)]
|
||||
(let [blocks (->> blocks
|
||||
(map (fn [^js el] (some->
|
||||
(.getAttribute el "blockid")
|
||||
(db-model/get-block-by-uuid)))))]
|
||||
(sdk-utils/result->js blocks)))))
|
||||
|
||||
(def clear_selected_blocks
|
||||
(fn []
|
||||
(state/clear-selection!)))
|
||||
|
||||
(def get_current_page
|
||||
(fn []
|
||||
(when-let [page (state/get-current-page)]
|
||||
(p/let [page (<get-block page {:children? false})]
|
||||
(when page
|
||||
(sdk-utils/result->js page))))))
|
||||
|
||||
(defn get_page
|
||||
[id-or-page-name]
|
||||
(p/let [page (<get-block id-or-page-name {:children? false})]
|
||||
(when page
|
||||
(sdk-utils/result->js page))))
|
||||
|
||||
(defn get_all_pages
|
||||
[]
|
||||
(p/let [result (db-async/<q
|
||||
(state/get-current-repo)
|
||||
{:transact-db? false}
|
||||
'[:find [(pull ?page [:db/id :block/uuid :block/name :block/title :block/created-at :block/updated-at]) ...]
|
||||
:where
|
||||
[?page :block/name]
|
||||
[(get-else $ ?page :logseq.property/hide? false) ?hide]
|
||||
[(false? ?hide)]])]
|
||||
(->> result
|
||||
(sort-by :block/title)
|
||||
sdk-utils/result->js)))
|
||||
|
||||
(defn create_page
|
||||
[name ^js properties ^js opts]
|
||||
(this-as
|
||||
this
|
||||
(let [properties (bean/->clj properties)
|
||||
db-based? (config/db-based-graph?)
|
||||
{:keys [redirect format journal schema class]} (bean/->clj opts)]
|
||||
(p/let [page (<get-block name {:children? false})
|
||||
new-page (when-not page
|
||||
(page-handler/<create!
|
||||
name
|
||||
(cond->
|
||||
{:redirect? (if (boolean? redirect) redirect true)
|
||||
:journal? journal
|
||||
:class? class
|
||||
:format format}
|
||||
(not db-based?)
|
||||
(assoc :properties properties))))
|
||||
_ (when (and db-based? (seq properties))
|
||||
(api-block/db-based-save-block-properties! new-page properties {:plugin this
|
||||
:schema schema}))]
|
||||
(some-> (or page new-page)
|
||||
sdk-utils/result->js)))))
|
||||
|
||||
(defn create_journal_page
|
||||
[^js date]
|
||||
(let [date (js/Date. date)]
|
||||
(when-let [datestr (and (not (js/isNaN (.getTime date)))
|
||||
(-> (gdate/Date. date)
|
||||
(date-time-util/format "yyyy-MM-dd")))]
|
||||
(create_page datestr nil #js {:journal true :redirect false}))))
|
||||
|
||||
(defn delete_page
|
||||
[name]
|
||||
(page-handler/<delete! name nil))
|
||||
|
||||
(def rename_page
|
||||
page-handler/rename!)
|
||||
|
||||
(defn open_in_right_sidebar
|
||||
[block-id-or-uuid]
|
||||
(editor-handler/open-block-in-sidebar!
|
||||
(if (number? block-id-or-uuid)
|
||||
block-id-or-uuid
|
||||
(sdk-utils/uuid-or-throw-error block-id-or-uuid))))
|
||||
|
||||
(defn new_block_uuid []
|
||||
(str (db/new-block-id)))
|
||||
|
||||
(def select_block
|
||||
(fn [block-uuid]
|
||||
(when-let [block (db-model/get-block-by-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
|
||||
(editor-handler/select-block! (:block/uuid block)) nil)))
|
||||
|
||||
(def edit_block
|
||||
(fn [block-uuid ^js opts]
|
||||
(when-let [block-uuid (and block-uuid (sdk-utils/uuid-or-throw-error block-uuid))]
|
||||
(when-let [block (db-model/query-block-by-uuid block-uuid)]
|
||||
(let [{:keys [pos] :or {pos :max}} (bean/->clj opts)]
|
||||
(editor-handler/edit-block! block pos {:container-id :unknown-container}))))))
|
||||
|
||||
(defn- <ensure-page-loaded
|
||||
[block-uuid-or-page-name]
|
||||
(p/let [repo (state/get-current-repo)
|
||||
block (db-async/<get-block repo (str block-uuid-or-page-name)
|
||||
{:children? true
|
||||
:include-collapsed-children? true})
|
||||
_ (when-let [page-id (:db/id (:block/page block))]
|
||||
(when-let [page-uuid (:block/uuid (db/entity page-id))]
|
||||
(db-async/<get-block repo page-uuid)))]
|
||||
block))
|
||||
|
||||
(defn insert_block
|
||||
[block-uuid-or-page-name content ^js opts]
|
||||
(this-as this
|
||||
(when (string/blank? block-uuid-or-page-name)
|
||||
(throw (js/Error. "Page title or block UUID shouldn't be empty.")))
|
||||
|
||||
(p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
|
||||
block (<get-block (str block-uuid-or-page-name))]
|
||||
(if (and block? (not block))
|
||||
(throw (js/Error. "Block not exists"))
|
||||
(p/let [{:keys [before start end sibling focus customUUID properties autoOrderedList schema]} (bean/->clj opts)
|
||||
[page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
|
||||
[nil (uuid block-uuid-or-page-name)]
|
||||
[block-uuid-or-page-name nil])
|
||||
page-name (when page-name (util/page-name-sanity-lc page-name))
|
||||
_ (when (and page-name
|
||||
(nil? (ldb/get-page (db/get-db) page-name)))
|
||||
(page-handler/<create! block-uuid-or-page-name {}))
|
||||
custom-uuid (or customUUID (:id properties))
|
||||
custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
|
||||
edit-block? (if (nil? focus) true focus)
|
||||
_ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
|
||||
(throw (js/Error.
|
||||
(util/format "Custom block UUID already exists (%s)." custom-uuid))))
|
||||
block-uuid' (if (and (not sibling) before block-uuid)
|
||||
(let [block (db/entity [:block/uuid block-uuid])
|
||||
first-child (ldb/get-first-child (db/get-db) (:db/id block))]
|
||||
(if first-child
|
||||
(:block/uuid first-child)
|
||||
block-uuid))
|
||||
block-uuid)
|
||||
insert-at-first-child? (not= block-uuid' block-uuid)
|
||||
[sibling? before?] (if insert-at-first-child?
|
||||
[true true]
|
||||
[sibling before])
|
||||
db-based? (config/db-based-graph?)
|
||||
before? (if (and (false? sibling?) before? (not insert-at-first-child?))
|
||||
false
|
||||
before?)
|
||||
opts' {:block-uuid block-uuid'
|
||||
:sibling? sibling?
|
||||
:before? before?
|
||||
:start? start
|
||||
:end? end
|
||||
:edit-block? edit-block?
|
||||
:page page-name
|
||||
:custom-uuid custom-uuid
|
||||
:ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
|
||||
:properties (when (not db-based?)
|
||||
(merge properties
|
||||
(when custom-uuid {:id custom-uuid})))}]
|
||||
(if db-based?
|
||||
(db-based-api/insert-block this content properties schema opts')
|
||||
(p/let [new-block (editor-handler/api-insert-new-block! content opts')]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))))
|
||||
|
||||
(def insert_batch_block
|
||||
(fn [block-uuid ^js batch-blocks-js ^js opts-js]
|
||||
(this-as
|
||||
this
|
||||
(p/let [block (<ensure-page-loaded block-uuid)]
|
||||
(when block
|
||||
(when-let [blocks (bean/->clj batch-blocks-js)]
|
||||
(let [db-based? (config/db-based-graph?)
|
||||
blocks' (if-not (vector? blocks) (vector blocks) blocks)
|
||||
opts (bean/->clj opts-js)
|
||||
{:keys [sibling before _schema keepUUID]} opts]
|
||||
(if db-based?
|
||||
(db-based-api/insert-batch-blocks this block blocks' opts)
|
||||
(let [keep-uuid? (or keepUUID false)
|
||||
_ (when keep-uuid? (doseq
|
||||
[block (outliner-core/tree-vec-flatten blocks' :children)]
|
||||
(let [uuid (:id (:properties block))]
|
||||
(when (and uuid (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error uuid)))
|
||||
(throw (js/Error.
|
||||
(util/format "Custom block UUID already exists (%s)." uuid)))))))
|
||||
block (if before
|
||||
(db/pull (:db/id (ldb/get-left-sibling (db/entity (:db/id block))))) block)
|
||||
sibling? (if (ldb/page? block) false sibling)]
|
||||
(p/let [result (editor-handler/insert-block-tree-after-target
|
||||
(:db/id block) sibling? blocks' (get block :block/format :markdown) keep-uuid?)
|
||||
blocks (:blocks result)]
|
||||
(let [blocks' (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)]
|
||||
(-> blocks'
|
||||
sdk-utils/normalize-keyword-for-json
|
||||
bean/->js))))))))))))
|
||||
|
||||
(def remove_block
|
||||
(fn [block-uuid ^js _opts]
|
||||
(p/let [repo (state/get-current-repo)
|
||||
_ (<get-block block-uuid {:children? false})]
|
||||
(editor-handler/delete-block-aux!
|
||||
{:block/uuid (sdk-utils/uuid-or-throw-error block-uuid) :repo repo}))))
|
||||
|
||||
(def update_block
|
||||
(fn [block-uuid content ^js opts]
|
||||
(this-as
|
||||
this
|
||||
(p/let [repo (state/get-current-repo)
|
||||
db-based? (config/db-based-graph?)
|
||||
block (<get-block block-uuid {:children? false})
|
||||
opts' (bean/->clj opts)]
|
||||
(when block
|
||||
(if db-based?
|
||||
(db-based-api/update-block this block content opts')
|
||||
(editor-handler/save-block! repo
|
||||
(sdk-utils/uuid-or-throw-error block-uuid) content
|
||||
(if db-based? (dissoc opts' :properties) opts'))))))))
|
||||
|
||||
(def move_block
|
||||
(fn [src-block-uuid target-block-uuid ^js opts]
|
||||
(p/let [_ (<get-block src-block-uuid {:children? false})
|
||||
_ (<get-block target-block-uuid {:children? false})]
|
||||
(let [{:keys [before children]} (bean/->clj opts)
|
||||
move-to (cond
|
||||
(boolean before)
|
||||
:top
|
||||
|
||||
(boolean children)
|
||||
:nested
|
||||
|
||||
:else
|
||||
nil)
|
||||
src-block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error src-block-uuid))
|
||||
target-block (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error target-block-uuid))]
|
||||
(editor-dnd-handler/move-blocks nil [src-block] target-block nil move-to)))))
|
||||
|
||||
(def get_block
|
||||
(fn [id ^js opts]
|
||||
(p/let [block (db-async/<get-block (state/get-current-repo) id {:children? true
|
||||
:include-collapsed-children? true})]
|
||||
(api-block/get_block (:db/id block) (or opts #js {:includePage true})))))
|
||||
|
||||
(def get_current_block
|
||||
(fn [^js opts]
|
||||
(let [block (state/get-edit-block)
|
||||
block (or block
|
||||
(some-> (or (first (state/get-selection-blocks))
|
||||
(state/get-editor-block-container))
|
||||
(.getAttribute "blockid")
|
||||
(db-model/get-block-by-uuid)))]
|
||||
(get_block (:block/uuid block) opts))))
|
||||
|
||||
(def get_previous_sibling_block
|
||||
(fn [block-uuid ^js opts]
|
||||
(p/let [id (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
block (<get-block id)
|
||||
;; Load all children blocks
|
||||
_ (api-block/<sync-children-blocks! block)]
|
||||
(when block
|
||||
(when-let [sibling (ldb/get-left-sibling (db/entity (:db/id block)))]
|
||||
(get_block (:block/uuid sibling) opts))))))
|
||||
|
||||
(def get_next_sibling_block
|
||||
(fn [block-uuid ^js opts]
|
||||
(p/let [id (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
block (<get-block id)
|
||||
;; Load all children blocks
|
||||
_ (api-block/<sync-children-blocks! block)]
|
||||
(when block
|
||||
(p/let [sibling (ldb/get-right-sibling (db/entity (:db/id block)))]
|
||||
(get_block (:block/uuid sibling) opts))))))
|
||||
|
||||
(def set_block_collapsed
|
||||
(fn [block-uuid ^js opts]
|
||||
(p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
block (<get-block block-uuid {:children? false})]
|
||||
(when block
|
||||
(let [opts (bean/->clj opts)
|
||||
opts (if (or (string? opts) (boolean? opts)) {:flag opts} opts)
|
||||
{:keys [flag]} opts
|
||||
flag (if (= "toggle" flag)
|
||||
(not (util/collapsed? block))
|
||||
(boolean flag))]
|
||||
(if flag
|
||||
(editor-handler/collapse-block! block-uuid)
|
||||
(editor-handler/expand-block! block-uuid))
|
||||
nil)))))
|
||||
|
||||
(def get_current_page_blocks_tree
|
||||
(fn []
|
||||
(when-let [page (state/get-current-page)]
|
||||
(let [page-id (:db/id (ldb/get-page (db/get-db) page))
|
||||
blocks (db-model/get-page-blocks-no-cache page-id)
|
||||
blocks (outliner-tree/blocks->vec-tree blocks page-id)
|
||||
;; clean key
|
||||
blocks (sdk-utils/normalize-keyword-for-json blocks)]
|
||||
(bean/->js blocks)))))
|
||||
|
||||
(def get_page_blocks_tree
|
||||
(fn [id-or-page-name]
|
||||
(p/let [_ (<ensure-page-loaded id-or-page-name)]
|
||||
(when-let [page-id (:db/id (db-model/get-page id-or-page-name))]
|
||||
(let [blocks (db-model/get-page-blocks-no-cache page-id)
|
||||
blocks (outliner-tree/blocks->vec-tree blocks page-id)
|
||||
blocks (sdk-utils/normalize-keyword-for-json blocks)]
|
||||
(bean/->js blocks))))))
|
||||
|
||||
(defn get_page_linked_references
|
||||
[page-name-or-uuid]
|
||||
(p/let [repo (state/get-current-repo)
|
||||
block (<get-block page-name-or-uuid {:children? false})]
|
||||
(when-let [id (:db/id block)]
|
||||
(p/let [result (db-async/<get-block-refs repo id)
|
||||
ref-blocks (db-utils/group-by-page result)]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json ref-blocks))))))
|
||||
|
||||
(defn prepend_block_in_page
|
||||
[uuid-or-page-name content ^js opts]
|
||||
(p/let [uuid-or-page-name (or
|
||||
uuid-or-page-name
|
||||
(state/get-current-page)
|
||||
(date/today))
|
||||
block (<get-block uuid-or-page-name)
|
||||
new-page (when (and (not block) (not (util/uuid-string? uuid-or-page-name))) ; page not exists
|
||||
(page-handler/<create! uuid-or-page-name
|
||||
{:redirect? false
|
||||
:format (state/get-preferred-format)}))]
|
||||
(let [block (or block new-page)
|
||||
opts (bean/->clj opts)
|
||||
opts' (assoc opts :before false :sibling false :start true)]
|
||||
(insert_block (str (:block/uuid block)) content (bean/->js opts')))))
|
||||
|
||||
(defn- get-current-page-or-today
|
||||
[]
|
||||
(or
|
||||
(state/get-current-page)
|
||||
(date/today)))
|
||||
|
||||
(defn append_block_in_page
|
||||
([content]
|
||||
(append_block_in_page (get-current-page-or-today) content nil))
|
||||
([uuid-or-page-name-or-content content-or-opts]
|
||||
(if (string? content-or-opts)
|
||||
(append_block_in_page uuid-or-page-name-or-content content-or-opts nil)
|
||||
(append_block_in_page (get-current-page-or-today) uuid-or-page-name-or-content content-or-opts)))
|
||||
([uuid-or-page-name content ^js opts]
|
||||
(let [uuid-or-page-name (or
|
||||
uuid-or-page-name
|
||||
(state/get-current-page)
|
||||
(date/today))]
|
||||
(p/let [_ (<ensure-page-loaded uuid-or-page-name)
|
||||
page? (not (util/uuid-string? uuid-or-page-name))
|
||||
page (db-model/get-page uuid-or-page-name)
|
||||
page-not-exist? (and page? (nil? page))
|
||||
new-page (when page-not-exist?
|
||||
(page-handler/<create! uuid-or-page-name
|
||||
{:redirect? false
|
||||
:format (state/get-preferred-format)}))
|
||||
block (or page new-page)]
|
||||
(let [children (:block/_parent block)
|
||||
[target sibling?] (if (seq children)
|
||||
[(last (ldb/sort-by-order children)) true]
|
||||
[block false])
|
||||
target-id (str (:block/uuid target))
|
||||
opts (-> (bean/->clj opts)
|
||||
(assoc :sibling sibling?))]
|
||||
(insert_block target-id content opts))))))
|
||||
|
||||
(defn download_graph_db
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(export-handler/export-repo-as-sqlite-db! repo)))
|
||||
|
||||
(defn download_graph_pages
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(export-handler/export-repo-as-zip! repo)))
|
||||
|
||||
(defn exec_git_command
|
||||
[^js args]
|
||||
(when-let [args (and args (seq (bean/->clj args)))]
|
||||
(shell/run-git-command! args)))
|
||||
|
||||
;; block properties
|
||||
(defn upsert_block_property
|
||||
[block-uuid key ^js value ^js options]
|
||||
(this-as
|
||||
this
|
||||
(p/let [key' (api-block/sanitize-user-property-name key)
|
||||
opts (bean/->clj options)
|
||||
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
repo (state/get-current-repo)
|
||||
block (<get-block block-uuid {:children? false})
|
||||
db-based? (config/db-based-graph?)
|
||||
value (bean/->clj value)]
|
||||
(when block
|
||||
(if db-based?
|
||||
(db-based-api/upsert-block-property this block key' value (:schema opts))
|
||||
(property-handler/set-block-property! repo block-uuid key' value))))))
|
||||
|
||||
(defn remove_block_property
|
||||
[block-uuid key]
|
||||
(this-as this
|
||||
(p/let [key (api-block/sanitize-user-property-name key)
|
||||
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
_block (<get-block block-uuid {:children? false})
|
||||
db-based? (config/db-based-graph?)
|
||||
key-ns? (namespace (keyword key))
|
||||
key (if (and db-based? (not key-ns?))
|
||||
(api-block/get-db-ident-from-property-name
|
||||
key (api-block/resolve-property-prefix-for-db this))
|
||||
key)]
|
||||
(property-handler/remove-block-property!
|
||||
(state/get-current-repo)
|
||||
block-uuid key))))
|
||||
|
||||
(defn get_block_property
|
||||
[block-uuid key]
|
||||
(this-as this
|
||||
(p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
_block (<get-block block-uuid {:children? false})]
|
||||
(when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
|
||||
(when (seq properties)
|
||||
(let [property-name (api-block/sanitize-user-property-name key)
|
||||
ident (api-block/get-db-ident-from-property-name
|
||||
property-name (api-block/resolve-property-prefix-for-db this))
|
||||
property-value (or (get properties property-name)
|
||||
(get properties (keyword property-name))
|
||||
(get properties ident))
|
||||
property-value (if-let [property-id (:db/id property-value)]
|
||||
(db/pull property-id) property-value)
|
||||
property-value (cond-> property-value
|
||||
(map? property-value)
|
||||
(assoc
|
||||
:value (or (:logseq.property/value property-value)
|
||||
(:block/title property-value))
|
||||
:ident ident))
|
||||
parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
|
||||
(or parsed-value
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
|
||||
|
||||
(def get_block_properties
|
||||
(fn [block-uuid]
|
||||
(p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
|
||||
block (<get-block block-uuid {:children? false})]
|
||||
(when block
|
||||
(let [properties (if (config/db-based-graph?)
|
||||
(api-block/into-readable-db-properties (:block/properties block))
|
||||
(:block/properties block))]
|
||||
(sdk-utils/result->js properties))))))
|
||||
|
||||
(defn get_page_properties
|
||||
[id-or-page-name]
|
||||
(p/let [page (<get-block id-or-page-name {:children? false})]
|
||||
(when-let [id (:block/uuid page)]
|
||||
(get_block_properties id))))
|
||||
81
src/main/logseq/api/file_based.cljs
Normal file
81
src/main/logseq/api/file_based.cljs
Normal file
@@ -0,0 +1,81 @@
|
||||
(ns logseq.api.file-based
|
||||
"File version related fns"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[frontend.db.async :as db-async]
|
||||
[frontend.db.file-based.model :as file-model]
|
||||
[frontend.db.model :as db-model]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.page :as page-handler]
|
||||
[frontend.handler.property :as property-handler]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.state :as state]
|
||||
[logseq.db.common.property-util :as db-property-util]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[logseq.sdk.utils :as sdk-utils]
|
||||
[promesa.core :as p]))
|
||||
|
||||
;; file-based templates
|
||||
|
||||
(defn get_current_graph_templates
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [templates (db-async/<get-all-templates repo)]
|
||||
(some-> templates
|
||||
(sdk-utils/normalize-keyword-for-json)
|
||||
(bean/->js)))))
|
||||
|
||||
(defn get_template
|
||||
[name]
|
||||
(p/let [block (when name (db-async/<get-template-by-name name))]
|
||||
(some-> block
|
||||
(sdk-utils/normalize-keyword-for-json)
|
||||
(bean/->js))))
|
||||
|
||||
(defn insert_template
|
||||
[target-uuid template-name]
|
||||
(p/let [exists? (page-handler/<template-exists? template-name)]
|
||||
(when exists?
|
||||
(when-let [target (db-model/get-block-by-uuid target-uuid)]
|
||||
(editor-handler/insert-template! nil template-name {:target target}) nil))))
|
||||
|
||||
(defn exist_template
|
||||
[name]
|
||||
(page-handler/<template-exists? name))
|
||||
|
||||
(defn create_template
|
||||
[target-uuid template-name ^js opts]
|
||||
(when (and template-name (db-model/get-block-by-uuid target-uuid))
|
||||
(p/let [{:keys [overwrite]} (bean/->clj opts)
|
||||
block (db-async/<get-template-by-name template-name)
|
||||
repo (state/get-current-repo)]
|
||||
(if (or (not block) (true? overwrite))
|
||||
(do (when-let [old-target block]
|
||||
(let [k (db-property-util/get-pid repo :logseq.property/template)]
|
||||
(property-handler/remove-block-property! repo (:block/uuid old-target) k)))
|
||||
(property-handler/set-block-property! repo target-uuid :logseq.property/template template-name))
|
||||
(throw (js/Error. "Template already exists!"))))))
|
||||
|
||||
(defn remove_template
|
||||
[name]
|
||||
(p/let [block (when name (db-async/<get-template-by-name name))]
|
||||
(when block
|
||||
(let [repo (state/get-current-repo)
|
||||
k (db-property-util/get-pid repo :logseq.property/template)]
|
||||
(property-handler/remove-block-property! repo (:block/uuid block) k)))))
|
||||
|
||||
(defn get_pages_from_namespace
|
||||
[ns]
|
||||
(when-let [repo (and ns (state/get-current-repo))]
|
||||
(when-let [pages (file-model/get-namespace-pages repo ns)]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
|
||||
|
||||
(defn get_pages_tree_from_namespace
|
||||
[ns]
|
||||
(when-let [repo (and ns (state/get-current-repo))]
|
||||
(when-let [pages (file-model/get-namespace-hierarchy repo ns)]
|
||||
(bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
|
||||
|
||||
(def set_blocks_id #(editor-handler/set-blocks-id! (map uuid %)))
|
||||
338
src/main/logseq/api/plugin.cljs
Normal file
338
src/main/logseq/api/plugin.cljs
Normal file
@@ -0,0 +1,338 @@
|
||||
(ns logseq.api.plugin
|
||||
"Plugin related apis"
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[cljs.reader]
|
||||
[clojure.string :as string]
|
||||
[electron.ipc :as ipc]
|
||||
[frontend.config :as config]
|
||||
[frontend.fs :as fs]
|
||||
[frontend.handler.command-palette :as palette-handler]
|
||||
[frontend.handler.common.plugin :as plugin-common-handler]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.idb :as idb]
|
||||
[frontend.modules.layout.core]
|
||||
[frontend.modules.shortcut.config :as shortcut-config]
|
||||
[frontend.modules.shortcut.core :as st]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[goog.object :as gobj]
|
||||
[lambdaisland.glogi :as log]
|
||||
[logseq.sdk.core]
|
||||
[logseq.sdk.experiments]
|
||||
[logseq.sdk.git]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn get-caller-plugin-id
|
||||
[] (gobj/get js/window "$$callerPluginID"))
|
||||
|
||||
;; helpers
|
||||
(defn install-plugin-hook
|
||||
[pid hook ^js opts]
|
||||
(state/install-plugin-hook pid hook (bean/->clj opts)))
|
||||
|
||||
(defn uninstall-plugin-hook
|
||||
[pid hook-or-all]
|
||||
(state/uninstall-plugin-hook pid hook-or-all))
|
||||
|
||||
(defn should-exec-plugin-hook
|
||||
[pid hook]
|
||||
(plugin-handler/plugin-hook-installed? pid hook))
|
||||
|
||||
(def load_plugin_config
|
||||
(fn [path]
|
||||
(if (util/electron?)
|
||||
(fs/read-file nil (util/node-path.join path "package.json"))
|
||||
(js/console.log "TODO: load plugin package.json from web plugin."))))
|
||||
|
||||
(def load_plugin_readme
|
||||
(fn [path]
|
||||
(fs/read-file nil (util/node-path.join path "readme.md"))))
|
||||
|
||||
(def save_plugin_package_json
|
||||
(fn [path ^js data]
|
||||
(let [repo ""
|
||||
path (util/node-path.join path "package.json")]
|
||||
(fs/write-plain-text-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true}))))
|
||||
|
||||
(defn- write_rootdir_file
|
||||
[file content sub-root root-dir]
|
||||
(p/let [repo ""
|
||||
path (util/node-path.join root-dir sub-root)
|
||||
exist? (fs/file-exists? path "")
|
||||
_ (when-not exist? (fs/mkdir-recur! path))
|
||||
user-path (util/node-path.join path file)
|
||||
sub-dir? (string/starts-with? user-path path)
|
||||
_ (when-not sub-dir?
|
||||
(log/info :debug user-path)
|
||||
(throw (js/Error. "write file denied")))
|
||||
user-path-root (util/node-path.dirname user-path)
|
||||
exist? (fs/file-exists? user-path-root "")
|
||||
_ (when-not exist? (fs/mkdir-recur! user-path-root))
|
||||
_ (fs/write-plain-text-file! repo nil user-path content {:skip-compare? true})]
|
||||
user-path))
|
||||
|
||||
(defn write_dotdir_file
|
||||
[file content sub-root]
|
||||
(some-> (plugin-handler/get-ls-dotdir-root)
|
||||
(p/then #(write_rootdir_file file content sub-root %))))
|
||||
|
||||
(defn write_assetsdir_file
|
||||
[file content sub-root]
|
||||
(if-let [assets-dir (config/get-current-repo-assets-root)]
|
||||
(write_rootdir_file file content sub-root assets-dir)
|
||||
false))
|
||||
|
||||
(defn- read_rootdir_file
|
||||
[file sub-root root-dir]
|
||||
(p/let [path (util/node-path.join root-dir sub-root)
|
||||
user-path (util/node-path.join path file)
|
||||
sub-dir? (string/starts-with? user-path path)
|
||||
_ (when-not sub-dir? (log/info :debug user-path) (throw (js/Error. "read file denied")))
|
||||
exist? (fs/file-exists? "" user-path)
|
||||
_ (when-not exist? (log/info :debug user-path) (throw (js/Error. "file not existed")))
|
||||
content (fs/read-file "" user-path)]
|
||||
content))
|
||||
|
||||
(defn- read_dotdir_file
|
||||
[file sub-root]
|
||||
(some-> (plugin-handler/get-ls-dotdir-root)
|
||||
(p/then #(read_rootdir_file file sub-root %))))
|
||||
|
||||
(defn- read_assetsdir_file
|
||||
[file sub-root]
|
||||
(when-let [root-dir (config/get-current-repo-assets-root)]
|
||||
(read_rootdir_file file sub-root root-dir)))
|
||||
|
||||
(defn- unlink_rootdir_file!
|
||||
[file sub-root root-dir]
|
||||
(p/let [repo ""
|
||||
path (util/node-path.join root-dir sub-root)
|
||||
user-path (util/node-path.join path file)
|
||||
sub-dir? (string/starts-with? user-path path)
|
||||
_ (when-not sub-dir? (log/info :debug user-path) (throw (js/Error. "access file denied")))
|
||||
exist? (fs/file-exists? "" user-path)
|
||||
_ (when-not exist? (log/info :debug user-path) (throw (js/Error. "file not existed")))
|
||||
_ (fs/unlink! repo user-path {})]))
|
||||
|
||||
(defn- unlink_dotdir_file!
|
||||
[file sub-root]
|
||||
(some-> (plugin-handler/get-ls-dotdir-root)
|
||||
(p/then #(unlink_rootdir_file! file sub-root %))))
|
||||
|
||||
(defn- unlink_assetsdir_file!
|
||||
[file sub-root]
|
||||
(when-let [root-dir (config/get-current-repo-assets-root)]
|
||||
(unlink_rootdir_file! file sub-root root-dir)))
|
||||
|
||||
(def write_user_tmp_file
|
||||
(fn [file content]
|
||||
(write_dotdir_file file content "tmp")))
|
||||
|
||||
(def write_plugin_storage_file
|
||||
(fn [plugin-id file content assets?]
|
||||
(let [plugin-id (util/node-path.basename plugin-id)
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
(if (true? assets?)
|
||||
(write_assetsdir_file file content sub-root)
|
||||
(write_dotdir_file file content sub-root)))))
|
||||
|
||||
(def read_plugin_storage_file
|
||||
(fn [plugin-id file assets?]
|
||||
(let [plugin-id (util/node-path.basename plugin-id)
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
(if (true? assets?)
|
||||
(read_assetsdir_file file sub-root)
|
||||
(read_dotdir_file file sub-root)))))
|
||||
|
||||
(def unlink_plugin_storage_file
|
||||
(fn [plugin-id file assets?]
|
||||
(let [plugin-id (util/node-path.basename plugin-id)
|
||||
sub-root (util/node-path.join "storages" plugin-id)]
|
||||
(if (true? assets?)
|
||||
(unlink_assetsdir_file! file sub-root)
|
||||
(unlink_dotdir_file! file sub-root)))))
|
||||
|
||||
(def exist_plugin_storage_file
|
||||
(fn [plugin-id file assets?]
|
||||
(p/let [root (if (true? assets?)
|
||||
(config/get-current-repo-assets-root)
|
||||
(plugin-handler/get-ls-dotdir-root))
|
||||
plugin-id (util/node-path.basename plugin-id)
|
||||
exist? (fs/file-exists?
|
||||
(util/node-path.join root "storages" plugin-id)
|
||||
file)]
|
||||
exist?)))
|
||||
|
||||
(def clear_plugin_storage_files
|
||||
(fn [plugin-id assets?]
|
||||
(p/let [root (if (true? assets?)
|
||||
(config/get-current-repo-assets-root)
|
||||
(plugin-handler/get-ls-dotdir-root))
|
||||
plugin-id (util/node-path.basename plugin-id)]
|
||||
(fs/rmdir! (util/node-path.join root "storages" plugin-id)))))
|
||||
|
||||
(def list_plugin_storage_files
|
||||
(fn [plugin-id assets?]
|
||||
(p/let [root (if (true? assets?)
|
||||
(config/get-current-repo-assets-root)
|
||||
(plugin-handler/get-ls-dotdir-root))
|
||||
plugin-id (util/node-path.basename plugin-id)
|
||||
files-path (util/node-path.join root "storages" plugin-id)
|
||||
^js files (ipc/ipc :listdir files-path)]
|
||||
(when (js-iterable? files)
|
||||
(bean/->js
|
||||
(map #(some-> (string/replace-first % files-path "")
|
||||
(string/replace #"^/+" "")) files))))))
|
||||
|
||||
(def load_user_preferences
|
||||
(fn []
|
||||
(let [repo ""
|
||||
path (plugin-handler/get-ls-dotdir-root)
|
||||
path (util/node-path.join path "preferences.json")]
|
||||
(if (util/electron?)
|
||||
(p/let [_ (fs/create-if-not-exists repo nil path)
|
||||
json (fs/read-file nil path)
|
||||
json (if (string/blank? json) "{}" json)]
|
||||
(js/JSON.parse json))
|
||||
(p/let [json (idb/get-item path)]
|
||||
(or json #js {}))))))
|
||||
|
||||
(def save_user_preferences
|
||||
(fn [^js data]
|
||||
(when data
|
||||
(let [repo ""
|
||||
path (plugin-handler/get-ls-dotdir-root)
|
||||
path (util/node-path.join path "preferences.json")]
|
||||
(if (util/electron?)
|
||||
(fs/write-plain-text-file! repo nil path (js/JSON.stringify data nil 2) {:skip-compare? true})
|
||||
(idb/set-item! path data))))))
|
||||
|
||||
(def load_plugin_user_settings
|
||||
;; results [path data]
|
||||
(plugin-handler/make-fn-to-load-dotdir-json "settings" #js {}))
|
||||
|
||||
(def save_plugin_user_settings
|
||||
(fn [key ^js data]
|
||||
((plugin-handler/make-fn-to-save-dotdir-json "settings")
|
||||
key data)))
|
||||
|
||||
(defn load_installed_web_plugins
|
||||
[]
|
||||
(let [getter (plugin-handler/make-fn-to-load-dotdir-json "installed-plugins-for-web" #js {})]
|
||||
(some-> (getter :all) (p/then second))))
|
||||
|
||||
(defn save_installed_web_plugin
|
||||
([^js plugin] (save_installed_web_plugin plugin false))
|
||||
([^js plugin remove?]
|
||||
(when-let [id (some-> plugin (.-key) (name))]
|
||||
(let [setter (plugin-handler/make-fn-to-save-dotdir-json "installed-plugins-for-web")
|
||||
plugin (js/JSON.parse (js/JSON.stringify plugin))]
|
||||
(p/let [^js plugins (or (load_installed_web_plugins) #js {})]
|
||||
(if (true? remove?)
|
||||
(when (aget plugins id)
|
||||
(js-delete plugins id))
|
||||
(gobj/set plugins id plugin))
|
||||
(setter :all plugins))))))
|
||||
|
||||
(defn unlink_installed_web_plugin
|
||||
[key]
|
||||
(save_installed_web_plugin #js {:key key} true))
|
||||
|
||||
(def unlink_plugin_user_settings
|
||||
(plugin-handler/make-fn-to-unlink-dotdir-json "settings"))
|
||||
|
||||
(def register_plugin_slash_command
|
||||
(fn [pid ^js cmd-actions]
|
||||
(when-let [[cmd actions] (bean/->clj cmd-actions)]
|
||||
(plugin-handler/register-plugin-slash-command
|
||||
pid [cmd (mapv #(into [(keyword (first %))]
|
||||
(rest %)) actions)]))))
|
||||
|
||||
(def register_plugin_simple_command
|
||||
(fn [pid ^js cmd-action palette?]
|
||||
(when-let [[cmd action] (bean/->clj cmd-action)]
|
||||
(let [action (assoc action 0 (keyword (first action)))
|
||||
cmd (assoc cmd :key (-> (:key cmd) (string/trim) (string/replace ":" "-") (string/replace #"^([0-9])" "_$1")))
|
||||
key (:key cmd)
|
||||
keybinding (:keybinding cmd)
|
||||
palette-cmd (plugin-handler/simple-cmd->palette-cmd pid cmd action)
|
||||
action' #(state/pub-event! [:exec-plugin-cmd {:type type :key key :pid pid :cmd cmd :action action}])]
|
||||
|
||||
;; handle simple commands
|
||||
(plugin-handler/register-plugin-simple-command pid cmd action)
|
||||
|
||||
;; handle palette commands
|
||||
(when palette?
|
||||
(palette-handler/register palette-cmd))
|
||||
|
||||
;; handle keybinding commands
|
||||
(when-let [shortcut-args (and keybinding (plugin-handler/simple-cmd-keybinding->shortcut-args pid key keybinding))]
|
||||
(let [dispatch-cmd (fn [_e]
|
||||
(if palette?
|
||||
(palette-handler/invoke-command palette-cmd)
|
||||
(action')))
|
||||
[mode-id id shortcut-map] (update shortcut-args 2 merge cmd {:fn dispatch-cmd :cmd palette-cmd})]
|
||||
|
||||
(cond
|
||||
;; FIX ME: move to register logic
|
||||
(= mode-id :shortcut.handler/block-editing-only)
|
||||
(shortcut-config/add-shortcut! mode-id id shortcut-map)
|
||||
|
||||
:else
|
||||
(do
|
||||
(println :shortcut/register-shortcut [mode-id id shortcut-map])
|
||||
(st/register-shortcut! mode-id id shortcut-map)))))))))
|
||||
|
||||
(defn unregister_plugin_simple_command
|
||||
[pid]
|
||||
;; remove simple commands
|
||||
(plugin-handler/unregister-plugin-simple-command pid)
|
||||
|
||||
;; remove palette commands
|
||||
(let [cmds-matched (->> (vals @shortcut-config/*shortcut-cmds)
|
||||
(filter #(string/includes? (str (:id %)) (str "plugin." pid))))]
|
||||
(when (seq cmds-matched)
|
||||
(doseq [cmd cmds-matched]
|
||||
(palette-handler/unregister (:id cmd))
|
||||
;; remove keybinding commands
|
||||
(when (seq (:shortcut cmd))
|
||||
(println :shortcut/unregister-shortcut cmd)
|
||||
(st/unregister-shortcut! (:handler-id cmd) (:id cmd)))))))
|
||||
|
||||
(defn register_search_service
|
||||
[pid name ^js opts]
|
||||
(plugin-handler/register-plugin-search-service pid name (bean/->clj opts)))
|
||||
|
||||
(defn unregister_search_services
|
||||
[pid]
|
||||
(plugin-handler/unregister-plugin-search-services pid))
|
||||
|
||||
(def register_plugin_ui_item
|
||||
(fn [pid type ^js opts]
|
||||
(when-let [opts (bean/->clj opts)]
|
||||
(plugin-handler/register-plugin-ui-item
|
||||
pid (assoc opts :type type)))))
|
||||
|
||||
(defn get_external_plugin
|
||||
[pid]
|
||||
(when-let [^js pl (plugin-handler/get-plugin-inst pid)]
|
||||
(.toJSON pl)))
|
||||
|
||||
(defn invoke_external_plugin_cmd
|
||||
[pid cmd-group cmd-key cmd-args]
|
||||
(case (keyword cmd-group)
|
||||
:models
|
||||
(plugin-handler/call-plugin-user-model! pid cmd-key cmd-args)
|
||||
|
||||
:commands
|
||||
(plugin-handler/call-plugin-user-command! pid cmd-key cmd-args)))
|
||||
|
||||
(defn validate_external_plugins [urls]
|
||||
(ipc/ipc :validateUserExternalPlugins urls))
|
||||
|
||||
(def __install_plugin
|
||||
(fn [^js manifest]
|
||||
(when-let [{:keys [repo id] :as manifest} (bean/->clj manifest)]
|
||||
(if-not (and repo id)
|
||||
(throw (js/Error. "[required] :repo :id"))
|
||||
(plugin-common-handler/install-marketplace-plugin! manifest)))))
|
||||
@@ -6,6 +6,7 @@
|
||||
[frontend.db :as db]
|
||||
[frontend.util :as util]
|
||||
[goog.object :as gobj]
|
||||
[logseq.db.common.entity-util :as common-entity-util]
|
||||
[logseq.db.frontend.content :as db-content]))
|
||||
|
||||
(defn- keep-json-keyword?
|
||||
@@ -14,17 +15,11 @@
|
||||
(contains? #{"block" "db" "file"})
|
||||
(not)))
|
||||
|
||||
(defn- entity->map
|
||||
"Convert a db Entity to a map"
|
||||
[e]
|
||||
(assert (de/entity? e))
|
||||
(assoc (into {} e) :db/id (:db/id e)))
|
||||
|
||||
(defn remove-hidden-properties
|
||||
[m]
|
||||
(->> (remove (fn [[k _v]]
|
||||
(or (= "block.temp" (namespace k))
|
||||
(contains? #{:logseq.property.embedding/hnsw-label-updated-at} k))) m)
|
||||
(contains? #{:logseq.property.embedding/hnsw-label-updated-at :block/tx-id} k))) m)
|
||||
(into {})))
|
||||
|
||||
(defn normalize-keyword-for-json
|
||||
@@ -32,9 +27,9 @@
|
||||
([input camel-case?]
|
||||
(when input
|
||||
(let [input (cond
|
||||
(de/entity? input) (entity->map input)
|
||||
(de/entity? input) (common-entity-util/entity->map input)
|
||||
(sequential? input) (map #(if (de/entity? %)
|
||||
(entity->map %)
|
||||
(common-entity-util/entity->map %)
|
||||
%) input)
|
||||
:else input)]
|
||||
(walk/prewalk
|
||||
@@ -50,7 +45,6 @@
|
||||
(de/entity? a) (:db/id a)
|
||||
(uuid? a) (str a)
|
||||
|
||||
;; @FIXME compatible layer for classic APIs
|
||||
(and (map? a) (:block/uuid a) (:block/title a))
|
||||
(-> a
|
||||
(assoc :block/content (:block/title a)
|
||||
@@ -85,6 +79,12 @@
|
||||
(reduce {} (gobj/getKeys obj)))
|
||||
obj))
|
||||
|
||||
(defn result->js
|
||||
[result]
|
||||
(-> result
|
||||
normalize-keyword-for-json
|
||||
bean/->js))
|
||||
|
||||
(def ^:export to-clj bean/->clj)
|
||||
(def ^:export jsx-to-clj jsx->clj)
|
||||
(def ^:export to-js bean/->js)
|
||||
|
||||
@@ -33,8 +33,10 @@
|
||||
["class1" "class2"]))
|
||||
(set (map :block/title (model/get-all-classes repo)))))))
|
||||
|
||||
;; TODO: Async test
|
||||
(deftest ^:fix-me get-class-objects-test
|
||||
(let [opts {:redirect? false}
|
||||
(let [opts {:class? true
|
||||
:redirect? false}
|
||||
_ (test-helper/create-page! "class1" opts)
|
||||
class (db/get-case-page "class1")
|
||||
_ (test-helper/save-block! repo fbid "Block 1" {:tags ["class1"]})]
|
||||
|
||||
@@ -294,11 +294,11 @@ prop-d:: [[nada]]"}])
|
||||
{:block/title "bug2"
|
||||
:build/tags [:Bug]}]}]})
|
||||
|
||||
(is (= ["task2" "bug2"]
|
||||
(map :block/title (dsl-query "(property status \"Todo\")")))
|
||||
(is (= #{"task2" "bug2"}
|
||||
(set (map :block/title (dsl-query "(property status \"Todo\")"))))
|
||||
"Blocks or tagged with or descended from a tag that has closed default-value property")
|
||||
(is (= ["task1" "bug1"]
|
||||
(map :block/title (dsl-query "(property status \"Doing\")")))
|
||||
(is (= #{"task1" "bug1"}
|
||||
(set (map :block/title (dsl-query "(property status \"Doing\")"))))
|
||||
"Blocks or tagged with or descended from a tag that don't have closed default-value property value")))
|
||||
|
||||
(deftest block-property-query-performance
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
[frontend.config :as config]
|
||||
[frontend.db :as db]
|
||||
[frontend.db.conn :as conn]
|
||||
[frontend.handler.db-based.page :as db-page-handler]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.file-based.repo :as file-repo-handler]
|
||||
[frontend.handler.file-based.status :as status]
|
||||
[frontend.state :as state]
|
||||
[frontend.worker.handler.page :as worker-page]
|
||||
[frontend.worker.pipeline :as worker-pipeline]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.common.order :as db-order]
|
||||
[logseq.db.sqlite.build :as sqlite-build]
|
||||
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
|
||||
[logseq.db.sqlite.util :as sqlite-util]
|
||||
[logseq.graph-parser.text :as text]
|
||||
[logseq.outliner.db-pipeline :as db-pipeline]))
|
||||
[logseq.graph-parser.text :as text]))
|
||||
|
||||
(def node? (exists? js/process))
|
||||
|
||||
@@ -33,9 +33,11 @@
|
||||
test-db' (if db-graph? test-db-name-db-version test-db-name)]
|
||||
(state/set-current-repo! test-db')
|
||||
(conn/start! test-db' opts)
|
||||
(ldb/register-transact-pipeline-fn!
|
||||
(fn [tx-report]
|
||||
(worker-pipeline/transact-pipeline test-db' tx-report)))
|
||||
(let [conn (conn/get-db test-db' false)]
|
||||
(when db-graph?
|
||||
(db-pipeline/add-listener conn)
|
||||
(d/transact! conn (sqlite-create-graph/build-db-initial-data "")))
|
||||
(d/listen! conn ::listen-db-changes!
|
||||
(fn [tx-report]
|
||||
@@ -187,7 +189,9 @@ This can be called in synchronous contexts as no async fns should be invoked"
|
||||
[;; page
|
||||
{:block/uuid page-uuid
|
||||
:block/name "test"
|
||||
:block/title "Test"}
|
||||
:block/title "Test"
|
||||
;; :block/tags #{:logseq.class/Page}
|
||||
}
|
||||
;; first block
|
||||
{:block/uuid first-block-uuid
|
||||
:block/page page-id
|
||||
@@ -238,7 +242,8 @@ This can be called in synchronous contexts as no async fns should be invoked"
|
||||
[repo block-uuid content {:keys [tags]}]
|
||||
(editor-handler/save-block! repo block-uuid content)
|
||||
(doseq [tag tags]
|
||||
(db-page-handler/add-tag repo block-uuid (db/get-page tag))))
|
||||
(db-property-handler/set-block-property! block-uuid :block/tags
|
||||
(db/get-page tag))))
|
||||
|
||||
(defn create-page!
|
||||
[title & {:as opts}]
|
||||
|
||||
@@ -172,10 +172,12 @@
|
||||
(outliner-core/insert-blocks!
|
||||
repo
|
||||
conn
|
||||
[{:block/uuid uuid1-client :block/title "uuid1-client"
|
||||
[{:block/uuid uuid1-client
|
||||
:block/title "uuid1-client"
|
||||
:block/order "a1"
|
||||
:block/parent [:block/uuid page-uuid]}
|
||||
{:block/uuid uuid2-client :block/title "uuid2-client"
|
||||
{:block/uuid uuid2-client
|
||||
:block/title "uuid2-client"
|
||||
:block/order "a2"
|
||||
:block/parent [:block/uuid page-uuid]}]
|
||||
(ldb/get-page @conn page-name)
|
||||
@@ -188,7 +190,10 @@
|
||||
{uuid1-remote {:op :move
|
||||
:self uuid1-remote
|
||||
:parents [page-uuid]
|
||||
:block/order "a0"}}}
|
||||
:block/order "a0"
|
||||
:block/title (ldb/write-transit-str "")
|
||||
:block/created-at (js/Date.now)
|
||||
:block/updated-at (js/Date.now)}}}
|
||||
move-ops (#'r.remote/move-ops-map->sorted-move-ops
|
||||
(:move-ops-map
|
||||
(#'r.remote/affected-blocks->diff-type-ops
|
||||
@@ -208,11 +213,17 @@
|
||||
{uuid2-remote {:op :move
|
||||
:self uuid2-remote
|
||||
:parents [uuid1-client]
|
||||
:block/order "a0"}
|
||||
:block/order "a0"
|
||||
:block/title (ldb/write-transit-str "")
|
||||
:block/created-at (js/Date.now)
|
||||
:block/updated-at (js/Date.now)}
|
||||
uuid1-remote {:op :move
|
||||
:self uuid1-remote
|
||||
:parents [uuid2-remote]
|
||||
:block/order "a1"}}}
|
||||
:block/order "a1"
|
||||
:block/title (ldb/write-transit-str "")
|
||||
:block/created-at (js/Date.now)
|
||||
:block/updated-at (js/Date.now)}}}
|
||||
move-ops (#'r.remote/move-ops-map->sorted-move-ops
|
||||
(:move-ops-map
|
||||
(#'r.remote/affected-blocks->diff-type-ops
|
||||
@@ -337,14 +348,18 @@ result:
|
||||
(let [repo (state/get-current-repo)
|
||||
conn (conn/get-db repo false)
|
||||
[page1-uuid ;; page2-uuid page3-uuid page4-uuid
|
||||
](repeatedly random-uuid)]
|
||||
](repeatedly random-uuid)
|
||||
page-tags [(:block/uuid (d/entity @conn :logseq.class/Page))]]
|
||||
(testing "apply-remote-update-page-ops-test1"
|
||||
(let [data-from-ws {:req-id "req-id" :t 1 :t-before 0
|
||||
:affected-blocks
|
||||
{page1-uuid {:op :update-page
|
||||
:self page1-uuid
|
||||
:page-name (ldb/write-transit-str (str "X" page1-uuid))
|
||||
:block/title (ldb/write-transit-str (str "X" page1-uuid))}}}
|
||||
:block/title (ldb/write-transit-str (str "X" page1-uuid))
|
||||
:block/tags page-tags
|
||||
:block/created-at (js/Date.now)
|
||||
:block/updated-at (js/Date.now)}}}
|
||||
update-page-ops (vals
|
||||
(:update-page-ops-map
|
||||
(#'r.remote/affected-blocks->diff-type-ops repo (:affected-blocks data-from-ws))))]
|
||||
@@ -358,7 +373,10 @@ result:
|
||||
{page1-uuid {:op :update-page
|
||||
:self page1-uuid
|
||||
:page-name (ldb/write-transit-str (str page1-uuid "-rename"))
|
||||
:block/title (ldb/write-transit-str (str page1-uuid "-rename"))}}}
|
||||
:block/title (ldb/write-transit-str (str page1-uuid "-rename"))
|
||||
:block/tags page-tags
|
||||
:block/created-at (js/Date.now)
|
||||
:block/updated-at (js/Date.now)}}}
|
||||
update-page-ops (vals
|
||||
(:update-page-ops-map
|
||||
(#'r.remote/affected-blocks->diff-type-ops repo (:affected-blocks data-from-ws))))]
|
||||
|
||||
Reference in New Issue
Block a user