Merge pull request #12130 from logseq/refactor/plugin-api-properties

refactor: Plugin apis
This commit is contained in:
Tienson Qin
2025-10-21 15:40:40 +08:00
committed by GitHub
80 changed files with 2671 additions and 1978 deletions

View File

@@ -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:

View File

@@ -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"))))))

View File

@@ -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"}

View File

@@ -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
View File

@@ -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"

View File

@@ -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"

View File

@@ -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]

View File

@@ -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"

View File

@@ -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
View File

@@ -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"}

View File

@@ -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"

View File

@@ -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}))))

View File

@@ -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?

View File

@@ -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)))

View File

@@ -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
(->>

View File

@@ -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)))

View File

@@ -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]

View File

@@ -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

View File

@@ -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"

View File

@@ -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))))))

View File

@@ -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))

View File

@@ -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)

View File

@@ -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
View File

@@ -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"

View File

@@ -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": {

View File

@@ -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))))

View File

@@ -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

View File

@@ -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"

View File

@@ -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!

View File

@@ -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

View File

@@ -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",

View File

@@ -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))

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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])))

View File

@@ -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))

View File

@@ -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"

View File

@@ -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")))

View File

@@ -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"

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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,

View File

@@ -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",

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)])))

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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

View File

@@ -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 {}))

View File

@@ -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}]

View File

@@ -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?

View File

@@ -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)))

View File

@@ -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]

View File

@@ -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)

View File

@@ -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'))))))))))

View File

@@ -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

View File

@@ -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

View File

@@ -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))))

View File

@@ -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))))

View File

@@ -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]

View File

@@ -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

View File

@@ -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]

View File

@@ -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.))

View File

@@ -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

View 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)))))

View File

@@ -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)))))))

View 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)))))

View 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)))))

View 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))))

View 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 %)))

View 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)))))

View File

@@ -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)

View File

@@ -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"]})]

View File

@@ -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

View File

@@ -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}]

View File

@@ -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))))]