Merge branch 'feat/db' into feat/hnswlib+transformer-js

This commit is contained in:
Tienson Qin
2025-03-16 11:52:32 +08:00
67 changed files with 3074 additions and 2586 deletions

View File

@@ -53,7 +53,7 @@ env:
jobs:
compile-cljs:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Check build options
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.build-target == 'nightly' || github.event.inputs.build-target == 'beta') && github.event.inputs.git-ref != 'master' }}
@@ -173,7 +173,7 @@ jobs:
e2e-test:
name: E2E Test Shard ${{ matrix.shard }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
matrix:
shard: [1, 2, 3]
@@ -183,7 +183,7 @@ jobs:
uses: actions/checkout@v3
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -223,11 +223,11 @@ jobs:
RELEASE: true # skip dev only test
build-linux-x64:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: [ compile-cljs ]
steps:
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -269,11 +269,11 @@ jobs:
path: builds
build-linux-arm64:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: [ compile-cljs ]
steps:
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -327,7 +327,7 @@ jobs:
needs: [ compile-cljs ]
steps:
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -394,7 +394,7 @@ jobs:
steps:
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -470,7 +470,7 @@ jobs:
steps:
- name: Download The Static Asset
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: static
path: static
@@ -560,7 +560,7 @@ jobs:
runs-on: [self-hosted, macos, token]
steps:
- name: Download Windows Artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-win64-unsigned-builds
path: ./builds
@@ -581,46 +581,46 @@ jobs:
nightly-release:
if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' }}
needs: [ build-macos-x64, build-macos-arm64, build-linux-x64, build-linux-arm64, codesign-windows, build-android, e2e-test ]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Download MacOS x64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-darwin-x64-builds
path: ./
- name: Download MacOS arm64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-darwin-arm64-builds
path: ./
- name: Download The Linux x64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-linux-x64-builds
path: ./
- name: Download The Linux arm64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-linux-arm64-builds
path: ./
- name: Download The Windows Artifact (Signed)
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-win64-signed-builds
path: ./
- name: Download The Windows Artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-win64-builds
path: ./
- name: Download Android Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-android-builds
path: ./
@@ -661,46 +661,46 @@ jobs:
# NOTE: For now, we only have beta channel to be released on Github
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build-target == 'beta' }}
needs: [ build-macos-x64, build-macos-arm64, build-linux-x64, build-linux-arm64, codesign-windows, build-android, e2e-test ]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Download MacOS x64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-darwin-x64-builds
path: ./
- name: Download MacOS arm64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-darwin-arm64-builds
path: ./
- name: Download The Linux x64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-linux-x64-builds
path: ./
- name: Download The Linux arm64 Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-linux-arm64-builds
path: ./
- name: Download The Windows Artifact (Signed)
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-win64-signed-builds
path: ./
- name: Download The Windows Artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-win64-builds
path: ./
- name: Download Android Artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
if: ${{ github.event_name == 'schedule' || github.event.inputs.build-android == 'true' }}
with:
name: logseq-android-builds

View File

@@ -12,7 +12,7 @@ env:
jobs:
build-docker:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout

View File

@@ -20,7 +20,7 @@ env:
jobs:
typos:
name: Spell Check with Typos
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
@@ -32,7 +32,7 @@ jobs:
test:
strategy:
matrix:
operating-system: [ubuntu-latest]
operating-system: [ubuntu-22.04]
runs-on: ${{ matrix.operating-system }}
@@ -90,7 +90,7 @@ jobs:
run: node static/tests.js -e fix-me
lint:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
@@ -180,7 +180,7 @@ jobs:
e2e-test:
# TODO: Re-enable when ready to enable tests for file graphs
if: false
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout

View File

@@ -25,7 +25,7 @@ env:
jobs:
e2e-test-build:
name: Build Test Artifact
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
@@ -98,7 +98,7 @@ jobs:
e2e-test-run:
needs: [ e2e-test-build ]
name: Test Shard ${{ matrix.shard }} Repeat ${{ matrix.repeat }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
matrix:
repeat: [1, 2]
@@ -112,7 +112,7 @@ jobs:
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Download test build artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: logseq-e2e-artifact

View File

@@ -3,15 +3,15 @@
(:require ["fs" :as fs]
["os" :as os]
["path" :as node-path]
#_:clj-kondo/ignore
[babashka.cli :as cli]
[clojure.edn :as edn]
[clojure.string :as string]
[datascript.core :as d]
#_:clj-kondo/ignore
[logseq.outliner.cli :as outliner-cli]
[validate-db]
[nbb.classpath :as cp]
[nbb.core :as nbb]))
[nbb.core :as nbb]
[validate-db]))
(defn- resolve-path
"If relative path, resolve with $ORIGINAL_PWD"

View File

@@ -2,13 +2,13 @@
"A script that dumps all eavt datoms to a specified edn file
$ yarn -s nbb-logseq script/dump_datoms.cljs db-name datoms.edn"
(:require [datascript.core :as d]
[clojure.pprint :as pprint]
[logseq.db.sqlite.cli :as sqlite-cli]
[nbb.core :as nbb]
["path" :as path]
(:require ["fs" :as fs]
["os" :as os]
["fs" :as fs]))
["path" :as path]
[clojure.pprint :as pprint]
[datascript.core :as d]
[logseq.db.sqlite.cli :as sqlite-cli]
[nbb.core :as nbb]))
(defn read-graph
"The db graph bare version of gp-cli/parse-graph"

View File

@@ -2,17 +2,17 @@
"A script that queries any db graph from the commandline e.g.
$ yarn -s nbb-logseq script/query.cljs db-name '[:find (pull ?b [:block/name :block/title]) :where [?b :block/created-at]]'"
(:require [datascript.core :as d]
[clojure.edn :as edn]
[logseq.db.sqlite.cli :as sqlite-cli]
[logseq.db.frontend.rules :as rules]
[nbb.core :as nbb]
[clojure.string :as string]
[clojure.pprint :as pprint]
[babashka.cli :as cli]
["child_process" :as child-process]
(:require ["child_process" :as child-process]
["os" :as os]
["path" :as node-path]
["os" :as os]))
[babashka.cli :as cli]
[clojure.edn :as edn]
[clojure.pprint :as pprint]
[clojure.string :as string]
[datascript.core :as d]
[logseq.db.frontend.rules :as rules]
[logseq.db.sqlite.cli :as sqlite-cli]
[nbb.core :as nbb]))
(defn- sh
"Run shell cmd synchronously and print to inherited streams by default. Aims
@@ -39,18 +39,15 @@
:desc "Print more info"}
:raw {:alias :r
:desc "Print results plainly. Useful when piped to bb"}
:additional-graphs {:alias :a
:coerce []
:desc "Additional graphs to query"}
:entity {:alias :e
:coerce []
:desc "Lookup entities instead of query"}})
(defn -main [args]
(let [{options :opts args' :args} (cli/parse-args args {:spec spec})
[graph-dir & args''] args'
_ (when (or (nil? graph-dir) (:help options))
(println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
(cli/format-opts {:spec spec})))
(js/process.exit 1))
[dir db-name] (get-dir-and-db-name graph-dir)
(defn query-graph [graph-dir args'' options]
(let [[dir db-name] (get-dir-and-db-name graph-dir)
conn (sqlite-cli/open-db! dir db-name)
results (if (:entity options)
(map #(when-let [ent (d/entity @conn
@@ -71,5 +68,18 @@
(sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
(pprint/pprint results)))))
(defn -main [args]
(let [{options :opts args' :args} (cli/parse-args args {:spec spec})
[graph-dir & args''] args'
_ (when (or (nil? graph-dir) (:help options))
(println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
(cli/format-opts {:spec spec})))
(js/process.exit 1))
graph-dirs (cond-> [graph-dir]
(:additional-graphs options)
(into (:additional-graphs options)))]
(doseq [graph-dir graph-dirs]
(query-graph graph-dir args'' options))))
(when (= nbb/*file* (nbb/invoked-file))
(-main *command-line-args*))

View File

@@ -178,7 +178,7 @@
(let [db-ident (get-ident all-idents prop-name)]
(db-property-build/build-closed-values
db-ident
prop-name
(:block/title prop-m)
(assoc prop-m :db/ident db-ident :closed-values closed-values)
{:property-attributes
(merge {:db/id (or (property-db-ids prop-name)
@@ -344,7 +344,7 @@
(mapcat (fn [m]
(if-let [pvalue-pages
(->> (vals (:build/properties m))
(mapcat #(if (set? %) % [%]) )
(mapcat #(if (set? %) % [%]))
(filter page-prop-value?)
(map second)
seq)]

View File

@@ -10,9 +10,9 @@
[logseq.db.frontend.content :as db-content]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.property :as db-property]
[logseq.db.sqlite.build :as sqlite-build]
[logseq.db.frontend.malli-schema :as db-malli-schema]))
[logseq.db.sqlite.build :as sqlite-build]))
;; Export fns
;; ==========

View File

@@ -1,14 +1,14 @@
(ns transact
"This script generically runs transactions against the queried blocks"
(:require [logseq.outliner.db-pipeline :as db-pipeline]
[logseq.db.sqlite.cli :as sqlite-cli]
[logseq.db.frontend.rules :as rules]
[datascript.core :as d]
(:require ["os" :as os]
["path" :as node-path]
[clojure.edn :as edn]
[clojure.string :as string]
[nbb.core :as nbb]
["path" :as node-path]
["os" :as os]))
[datascript.core :as d]
[logseq.db.frontend.rules :as rules]
[logseq.db.sqlite.cli :as sqlite-cli]
[logseq.outliner.db-pipeline :as db-pipeline]
[nbb.core :as nbb]))
(defn -main [args]
(when (< (count args) 3)

View File

@@ -148,34 +148,6 @@
:property-id (:db/id property)}))
property))
(defn upsert-property!
"Updates property if property-id is given. Otherwise creates a property
with the given property-id or :property-name option. When a property is created
it is ensured to have a unique :db/ident"
[conn property-id schema {:keys [property-name] :as opts}]
(let [db @conn
db-ident (or property-id
(try (db-property/create-user-property-ident-from-name property-name)
(catch :default e
(throw (ex-info (str e)
{:type :notification
:payload {:message "Property failed to create. Please try a different property name."
:type :error}})))))]
(assert (qualified-keyword? db-ident))
(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))
(name property-id))
db-ident' (db-ident/ensure-unique-db-ident @conn db-ident)]
(assert (some? k-name)
(prn "property-id: " property-id ", property-name: " property-name))
(outliner-validate/validate-page-title k-name {:node {:db/ident db-ident'}})
(outliner-validate/validate-page-title-characters k-name {:node {:db/ident db-ident'}})
(ldb/transact! conn
[(sqlite-util/build-new-property db-ident' schema {:title k-name})]
{:outliner-op :new-property})
(d/entity @conn db-ident')))))
(defn- validate-property-value-aux
[schema value {:keys [many?]}]
;; normalize :many values since most components update them as a single value
@@ -250,12 +222,12 @@
[conn property-id v]
(let [property (d/entity @conn property-id)
closed-values? (seq (:property/closed-values property))
default-type? (= :default (:logseq.property/type property))]
default-or-url? (contains? #{:default :url} (:logseq.property/type property))]
(cond
closed-values?
(get-property-value-eid @conn property-id v)
(and default-type?
(and default-or-url?
;; FIXME: remove this when :logseq.property/order-list-type updated to closed values
(not= property-id :logseq.property/order-list-type))
(let [v-uuid (create-property-text-block! conn nil property-id v {})]
@@ -288,72 +260,6 @@
:payload {:message "Can't set this block itself as own property value"
:type :error}}))))
(defn set-block-property!
"Updates a block property's value for an existing property-id and block. If
property is a ref type, automatically handles a raw property value i.e. you
can pass \"value\" instead of the property value entity. Also handle db
attributes as properties"
[conn block-eid property-id v]
(throw-error-if-read-only-property 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/parent)
(outliner-validate/validate-parent-property v [block]))
(cond
db-attribute?
(when-not (and (= property-id :block/alias) (= v (:db/id block))) ; alias can't be itself
(ldb/transact! conn [{:db/id (:db/id block) property-id v}]
{: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))))))
(defn batch-set-property!
"Sets properties for multiple blocks. Automatically handles property value refs.
Does no validation of property values."
[conn block-ids property-id v]
(assert property-id "property-id is nil")
(throw-error-if-read-only-property property-id)
(let [block-eids (map ->eid block-ids)
_ (when (= property-id :block/tags)
(outliner-validate/validate-tags-property @conn block-eids v))
property (d/entity @conn property-id)
_ (when (= (:db/ident property) :logseq.property/parent)
(outliner-validate/validate-parent-property
(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"))
property-type (get property :logseq.property/type :default)
_ (assert (some? v) "Can't set a nil property value must be not nil")
ref? (db-property-type/value-ref-property-types property-type)
v' (if ref?
(convert-ref-property-value conn property-id v property-type)
v)]
(doseq [eid block-eids]
(let [block (d/entity @conn eid)]
(throw-error-if-self-value block v' ref?)))
(let [txs (mapcat
(fn [eid]
(if-let [block (d/entity @conn eid)]
(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)]
(when (seq txs)
(ldb/transact! conn txs {:outliner-op :save-block})))))
(defn batch-remove-property!
[conn block-ids property-id]
(throw-error-if-read-only-property property-id)
@@ -386,6 +292,46 @@
(when (seq txs)
(ldb/transact! conn txs {:outliner-op :save-block})))))))
(defn batch-set-property!
"Sets properties for multiple blocks. Automatically handles property value refs.
Does no validation of property values."
[conn block-ids property-id v]
(assert property-id "property-id is nil")
(throw-error-if-read-only-property property-id)
(if (nil? v)
(batch-remove-property! conn block-ids property-id)
(let [block-eids (map ->eid block-ids)
_ (when (= property-id :block/tags)
(outliner-validate/validate-tags-property @conn block-eids v))
property (d/entity @conn property-id)
_ (when (= (:db/ident property) :logseq.property/parent)
(outliner-validate/validate-parent-property
(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"))
property-type (get property :logseq.property/type :default)
_ (assert (some? v) "Can't set a nil property value must be not nil")
ref? (db-property-type/value-ref-property-types property-type)
default-url-not-closed? (and (contains? #{:default :url} property-type)
(not (seq (:property/closed-values property))))
v' (if ref?
(convert-ref-property-value conn property-id v property-type)
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))
v')]
(throw-error-if-self-value block v' ref?)
(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))]
(when (seq txs)
(ldb/transact! conn txs {:outliner-op :save-block})))))
(defn remove-block-property!
[conn eid property-id]
(throw-error-if-read-only-property property-id)
@@ -415,6 +361,69 @@
:else
(batch-remove-property! conn [eid] property-id))))
(defn set-block-property!
"Updates a block property's value for an existing property-id and block. If
property is a ref type, automatically handles a raw property value i.e. you
can pass \"value\" instead of the property value entity. Also handle db
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/parent)
(outliner-validate/validate-parent-property v [block]))
(cond
db-attribute?
(when-not (and (= property-id :block/alias) (= v (:db/id block))) ; alias can't be itself
(ldb/transact! conn [{:db/id (:db/id block) property-id v}]
{: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)))))))
(defn upsert-property!
"Updates property if property-id is given. Otherwise creates a property
with the given property-id or :property-name option. When a property is created
it is ensured to have a unique :db/ident"
[conn property-id schema {:keys [property-name] :as opts}]
(let [db @conn
db-ident (or property-id
(try (db-property/create-user-property-ident-from-name property-name)
(catch :default e
(throw (ex-info (str e)
{:type :notification
:payload {:message "Property failed to create. Please try a different property name."
:type :error}})))))]
(assert (qualified-keyword? db-ident))
(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))
(name property-id))
db-ident' (db-ident/ensure-unique-db-ident @conn db-ident)]
(assert (some? k-name)
(prn "property-id: " property-id ", property-name: " property-name))
(outliner-validate/validate-page-title k-name {:node {:db/ident db-ident'}})
(outliner-validate/validate-page-title-characters k-name {:node {:db/ident db-ident'}})
(ldb/transact! conn
[(sqlite-util/build-new-property db-ident' schema {:title k-name})]
{:outliner-op :new-property})
(d/entity @conn db-ident')))))
(defn delete-property-value!
"Delete value if a property has multiple values"
[conn block-eid property-id property-value]

View File

@@ -1,13 +1,13 @@
(ns publishing
"Basic script for publishing from CLI"
(:require [logseq.graph-parser.cli :as gp-cli]
[logseq.publishing :as publishing]
[logseq.db.sqlite.cli :as sqlite-cli]
["fs" :as fs]
(:require ["fs" :as fs]
["path" :as node-path]
[clojure.edn :as edn]
[datascript.core :as d]
[logseq.db.sqlite.cli :as sqlite-cli]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.graph-parser.cli :as gp-cli]
[logseq.publishing :as publishing]
[nbb.core :as nbb]))
(defn- get-db [graph-dir]

View File

@@ -1,9 +1,9 @@
(ns logseq.shui.popup.core
(:require [rum.core :as rum]
(:require [dommy.core :as d]
[logseq.shui.util :as util]
[medley.core :as medley]
[logseq.shui.util :refer [use-atom]]
[dommy.core :as d]))
[medley.core :as medley]
[rum.core :as rum]))
;; ui
(def button (util/lsui-wrap "Button"))
@@ -39,7 +39,7 @@
[id]
(when id
(some->> (medley/indexed @*popups)
(filter #(= id (:id (second %)))) (first))))
(filter #(= id (:id (second %)))) (first))))
(defn get-popups [] @*popups)
(defn get-last-popup [] (last @*popups))
@@ -97,13 +97,14 @@
:start 0
:end width
(/ width 2)))
(- bottom height) width height])
(- bottom height)
width height])
:else [0 0])]
(reset! *last-show-target @*target)
(js/setTimeout #(reset! *last-show-target nil) 64)
(some-> @*target
(d/set-attr! "data-popup-active"
(if (keyword? id) (name id) (str id))))
(d/set-attr! "data-popup-active"
(if (keyword? id) (name id) (str id))))
(upsert-popup!
(merge opts
{:id id :target (deref *target)
@@ -166,32 +167,32 @@
(when-not (false? (some-> content-props (:onPointerDownOutside) (apply [e])))
(hide! id 1)))]
(popup-root
(merge root-props {:open open?})
(popup-trigger
{:as-child true}
(button {:class "overflow-hidden fixed p-0 opacity-0"
:style {:height (if (and (number? height)
(> height 0))
height 1)
:width 1
:top y
:left x}} ""))
(let [content-props (cond-> (merge content-props {:onEscapeKeyDown handle-key-escape!
:disableOutsideScroll false
:onPointerDownOutside handle-pointer-outside!})
(and (not force-popover?)
(not as-dropdown?))
(assoc :on-key-down (fn [^js e]
(some-> content-props :on-key-down (apply [e]))
(set! (. e -defaultPrevented) true))
:on-pointer-move #(set! (. % -defaultPrevented) true)))
content (if (fn? content)
(content (cond-> {:id id}
as-content?
(assoc :content-props content-props))) content)]
(if as-content?
content
(popup-content content-props content)))))))
(merge root-props {:open open?})
(popup-trigger
{:as-child true}
(button {:class "overflow-hidden fixed p-0 opacity-0"
:style {:height (if (and (number? height)
(> height 0))
height 1)
:width 1
:top y
:left x}} ""))
(let [content-props (cond-> (merge content-props {:onEscapeKeyDown handle-key-escape!
:disableOutsideScroll false
:onPointerDownOutside handle-pointer-outside!})
(and (not force-popover?)
(not as-dropdown?))
(assoc :on-key-down (fn [^js e]
(some-> content-props :on-key-down (apply [e]))
(set! (. e -defaultPrevented) true))
:on-pointer-move #(set! (. % -defaultPrevented) true)))
content (if (fn? content)
(content (cond-> {:id id}
as-content?
(assoc :content-props content-props))) content)]
(if as-content?
content
(popup-content content-props content)))))))
(rum/defc install-popups
< rum/static

View File

@@ -1,21 +1,22 @@
(ns logseq.shui.ui
(:require [logseq.shui.util :as util]
(:require [logseq.shui.base.core :as base-core]
[logseq.shui.dialog.core :as dialog-core]
[logseq.shui.form.core :as form-core]
[logseq.shui.icon.v2 :as icon-v2]
[logseq.shui.shortcut.v1 :as shui.shortcut.v1]
[logseq.shui.toaster.core :as toaster-core]
[logseq.shui.popup.core :as popup-core]
[logseq.shui.select.core :as select-core]
[logseq.shui.select.multi :as select-multi]
[logseq.shui.dialog.core :as dialog-core]
[logseq.shui.popup.core :as popup-core]
[logseq.shui.base.core :as base-core]
[logseq.shui.form.core :as form-core]
[logseq.shui.table.core :as table-core]))
[logseq.shui.shortcut.v1 :as shui.shortcut.v1]
[logseq.shui.table.core :as table-core]
[logseq.shui.toaster.core :as toaster-core]
[logseq.shui.util :as util]))
(def button base-core/button)
(def button-icon base-core/button-icon)
(def button-ghost-icon base-core/button-ghost-icon)
(def button-outline-icon base-core/button-outline-icon)
(def button-secondary-icon base-core/button-secondary-icon)
(def button-group (util/lsui-wrap "ButtonGroup"))
(def link base-core/link)
(def trigger-as base-core/trigger-as)
(def trigger-child-wrap base-core/trigger-child-wrap)

View File

@@ -6,7 +6,7 @@
"devDependencies": {
"@axe-core/playwright": "=4.4.4",
"@capacitor/cli": "^5.0.0",
"@playwright/test": "=1.44.0",
"@playwright/test": "=1.51.0",
"@tailwindcss/aspect-ratio": "0.4.2",
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/typography": "0.5.7",
@@ -24,7 +24,7 @@
"karma-chrome-launcher": "^3.2.0",
"karma-cljs-test": "^0.1.0",
"npm-run-all": "^4.1.5",
"playwright": "=1.44.0",
"playwright": "=1.51.0",
"postcss": "^8.4.47",
"postcss-cli": "10.0.0",
"postcss-functions": "^4.0.2",
@@ -136,8 +136,8 @@
"d3-force": "3.0.0",
"diff": "5.0.0",
"dompurify": "2.4.0",
"electron": "31.7.5",
"electron-dl": "3.3.0",
"electron": "35.0.1",
"electron-dl": "^4.0.0",
"emoji-mart": "^5.5.2",
"fs": "0.0.1-security",
"fs-extra": "9.1.0",

View File

@@ -0,0 +1,53 @@
import { Children, ReactElement, cloneElement } from 'react';
import { ButtonProps } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface ButtonGroupProps {
className?: string;
orientation?: 'horizontal' | 'vertical';
children: ReactElement<ButtonProps>[];
}
export const ButtonGroup = ({
className,
orientation = 'horizontal',
children,
}: ButtonGroupProps) => {
const totalButtons = Children.count(children);
const isHorizontal = orientation === 'horizontal';
const isVertical = orientation === 'vertical';
return (
<div
className={cn(
'flex',
{
'flex-col': isVertical,
'w-fit': isVertical,
},
className
)}
>
{Children.map(children, (child, index) => {
const isFirst = index === 0;
const isLast = index === totalButtons - 1;
return cloneElement(child, {
className: cn(
{
'rounded-l-none': isHorizontal && !isFirst,
'rounded-r-none': isHorizontal && !isLast,
'border-l-0': isHorizontal && !isFirst,
'rounded-t-none': isVertical && !isFirst,
'rounded-b-none': isVertical && !isLast,
'border-t-0': isVertical && !isFirst,
},
child.props.className
),
});
})}
</div>
);
};

View File

@@ -1,4 +1,5 @@
import { Button } from '@/components/ui/button'
import { ButtonGroup } from '@/components/ui/button-group'
import { Slider, SliderTrack, SliderRange, SliderThumb } from '@/components/ui/slider'
import {
DropdownMenu,
@@ -99,7 +100,7 @@ declare global {
}
const shadui = {
Link, Button,
Link, Button, ButtonGroup,
Slider, SliderTrack, SliderRange, SliderThumb,
DropdownMenu,
DropdownMenuContent,

View File

@@ -14,7 +14,7 @@
"electron:make-linux-arm64": "electron-forge make --platform=linux --arch=arm64",
"electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
"electron:publish:github": "electron-forge publish",
"rebuild:all": "electron-rebuild -v 28.3.1 -f",
"rebuild:all": "electron-rebuild -v 35.0.1 -f",
"postinstall": "install-app-deps"
},
"config": {
@@ -54,16 +54,16 @@
"@electron-forge/maker-squirrel": "^7.3.1",
"@electron-forge/maker-zip": "^7.3.1",
"@electron/rebuild": "3.2.10",
"electron": "31.7.5",
"electron": "35.0.1",
"electron-builder": "25.1.8",
"electron-forge-maker-appimage": "https://github.com/logseq/electron-forge-maker-appimage.git",
"electron-devtools-installer": "^3.2.0"
},
"resolutions": {
"**/electron": "31.7.5",
"**/node-abi": "3.68.0",
"**/node-gyp": "10.0.1",
"string-width": "^4.2.0",
"**/electron": "35.0.1",
"**/node-abi": "3.74.0",
"**/node-gyp": "11.1.0",
"string-width": "4.2.0",
"wrap-ansi": "^7.0.0",
"strip-ansi": "^6.0.1"
}

View File

@@ -1,11 +1,11 @@
(ns logseq.tasks.db-graph.create-graph-with-large-sizes
"Script that generates graphs at large sizes"
(:require [logseq.outliner.cli :as outliner-cli]
(:require ["os" :as os]
["path" :as node-path]
[babashka.cli :as cli]
[clojure.string :as string]
[datascript.core :as d]
[babashka.cli :as cli]
["path" :as node-path]
["os" :as os]
[logseq.outliner.cli :as outliner-cli]
[nbb.classpath :as cp]
[nbb.core :as nbb]))

View File

@@ -2,12 +2,12 @@
"This ns is a wrapper around the chokidar file watcher,
https://www.npmjs.com/package/chokidar. File watcher events are sent to the
`file-watcher` ipc channel"
(:require [cljs-bean.core :as bean]
["fs" :as fs]
["chokidar" :as watcher]
[electron.utils :as utils]
[electron.logger :as logger]
(:require ["chokidar" :as watcher]
["electron" :refer [app]]
["fs" :as fs]
[cljs-bean.core :as bean]
[electron.logger :as logger]
[electron.utils :as utils]
[electron.window :as window]
[logseq.common.graph :as common-graph]))
@@ -52,7 +52,7 @@
(utils/read-file path))
stat (when (and (not= event "unlink")
(not dir-path?))
(fs/statSync path))]
(utils/fs-stat->clj path))]
(send-file-watcher! dir event (merge {:dir (utils/fix-win-path! dir)
:path (utils/fix-win-path! path)
:content content

View File

@@ -123,7 +123,7 @@
(when (and (chmod-enabled?) (fs/existsSync path) (not (writable? path)))
(fs/chmodSync path "644"))
(fs/writeFileSync path content)
(fs/statSync path)
(utils/fs-stat->clj path)
(catch :default e
(logger/warn ::write-file path e)
(let [backup-path (try
@@ -144,7 +144,7 @@
(fs/renameSync old-path new-path))
(defmethod handle :stat [_window [_ path]]
(fs/statSync path))
(utils/fs-stat->clj path))
(defn- get-files
"Returns vec of file-objs"

View File

@@ -3,12 +3,12 @@
["electron" :refer [app BrowserWindow]]
["fs-extra" :as fs]
["path" :as node-path]
[cljs-bean.core :as bean]
[clojure.string :as string]
[electron.configs :as cfgs]
[electron.db :as db]
[electron.logger :as logger]
[logseq.db.sqlite.util :as sqlite-util]
[cljs-bean.core :as bean]
[electron.db :as db]
[promesa.core :as p]))
(defonce *win (atom nil)) ;; The main window
@@ -151,7 +151,6 @@
(logger/warn "Unknown PAC rule:" line)
nil))))
(defn <get-system-proxy
"Get system proxy for url, requires proxy to be set to system"
([] (<get-system-proxy "https://www.google.com"))
@@ -295,3 +294,10 @@
(catch :default _
(println "decodeURIComponent failed: " uri)
uri)))
(defn fs-stat->clj
[path]
(let [stat (fs/statSync path)]
{:size (.-size stat)
:mtime (.-mtime stat)
:ctime (.-ctime stat)}))

View File

@@ -1084,9 +1084,13 @@
(not contents-page?))
[:span.text-gray-500.bracket page-ref/left-brackets])
(when (and (config/db-based-graph?) (ldb/class-instance? (db/entity :logseq.class/Task) block))
[:div.inline-block {:style {:margin-right 1}
:on-pointer-down (fn [e]
(util/stop e))} (block-positioned-properties config block :block-left)])
[:div.inline-block
{:style {:margin-right 1
:margin-top -2
:vertical-align "middle"}
:on-pointer-down (fn [e]
(util/stop e))}
(block-positioned-properties config block :block-left)])
(page-cp config' (if (uuid? uuid-or-title)
{:block/uuid uuid-or-title}
{:block/name uuid-or-title}))
@@ -2162,7 +2166,8 @@
(not edit?)
(not (:block.temp/top? block))
(not (:block.temp/bottom? block))
(not (util/react *control-show?)))
(not (util/react *control-show?))
(not (:logseq.property/created-from-property block)))
(and doc-mode?
(not collapsed?)
(not (util/react *control-show?))))

View File

@@ -383,7 +383,7 @@
}
.page-reference {
@apply inline-flex flex-row items-center rounded transition-[background];
@apply rounded transition-[background];
.bracket {
@apply opacity-30;

View File

@@ -878,16 +878,18 @@
{:keys [page page-entity]} (state/sub :page-title/context)]
(let [show!
(fn [content]
(fn [content & {:as option}]
(shui/popup-show! e
(fn [{:keys [id]}]
[:div {:on-click #(shui/popup-hide! id)
:data-keep-selection true}
content])
{:on-before-hide state/dom-clear-selection!
:on-after-hide state/state-clear-selection!
:content-props {:class "w-[280px] ls-context-menu-content"}
:as-dropdown? true}))
(merge
{:on-before-hide state/dom-clear-selection!
:on-after-hide state/state-clear-selection!
:content-props {:class "w-[280px] ls-context-menu-content"}
:as-dropdown? true}
option)))
handled
(cond
@@ -901,11 +903,12 @@
(show! (cp-content/block-ref-custom-context-menu-content block block-ref))
(state/set-state! :block-ref/context nil))
;; block selection
;; block selection
(and (state/selection?) (not (d/has-class? target "bullet")))
(show! (cp-content/custom-context-menu-content))
(show! (cp-content/custom-context-menu-content)
{:id :blocks-selection-context-menu})
;; block bullet
;; block bullet
(and block-id (parse-uuid block-id))
(let [block (.closest target ".ls-block")
property-default-value? (when block
@@ -922,6 +925,10 @@
[]
nil)
(defn- on-mouse-up
[_e]
(editor-handler/show-action-bar!))
(rum/defcs ^:large-vars/cleanup-todo root-container < rum/reactive
(mixins/event-mixin
(fn [state]
@@ -1014,6 +1021,7 @@
(ui/focus-element (ui/main-node))))}
(t :accessibility/skip-to-main-content)]
[:div.#app-container
{:on-mouse-up on-mouse-up}
[:div#left-container
{:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
(header/header {:light? light?

View File

@@ -71,7 +71,7 @@
(shui/dropdown-menu-item
{:key "copy"
:on-click editor-handler/copy-selection-blocks}
:on-click #(editor-handler/copy-selection-blocks true)}
(t :editor/copy)
(shui/dropdown-menu-shortcut (ui/keyboard-shortcut-from-config :editor/copy)))

View File

@@ -661,63 +661,63 @@
(rum/defc shui-editor-popups
[id format action _data]
(hooks/use-effect!
(fn []
(let [pid (case action
:commands
(open-editor-popup! :commands
(commands id format)
{:content-props {:withoutAnimation false}})
(fn []
(let [pid (case action
:commands
(open-editor-popup! :commands
(commands id format)
{:content-props {:withoutAnimation false}})
(:block-search :page-search :page-search-hashtag)
(open-editor-popup! action
(if (= :block-search action)
(block-search id format)
(page-search id format))
{:root-props {:onOpenChange
#(when-not %
(when (contains?
#{:block-search :page-search :page-search-hashtag}
(state/get-editor-action))
(state/clear-editor-action!)))}})
(:block-search :page-search :page-search-hashtag)
(open-editor-popup! action
(if (= :block-search action)
(block-search id format)
(page-search id format))
{:root-props {:onOpenChange
#(when-not %
(when (contains?
#{:block-search :page-search :page-search-hashtag}
(state/get-editor-action))
(state/clear-editor-action!)))}})
:datepicker
(open-editor-popup! :datepicker
(datetime-comp/date-picker id format nil) {})
:datepicker
(open-editor-popup! :datepicker
(datetime-comp/date-picker id format nil) {})
:input
(open-editor-popup! :input
(editor-input id
:input
(open-editor-popup! :input
(editor-input id
;; on-submit
(fn [command m]
(editor-handler/handle-command-input command id format m))
(fn [command m]
(editor-handler/handle-command-input command id format m))
;; on-cancel
(fn []
(editor-handler/handle-command-input-close id)))
{:content-props {:onOpenAutoFocus #()}})
(fn []
(editor-handler/handle-command-input-close id)))
{:content-props {:onOpenAutoFocus #()}})
:select-code-block-mode
(open-editor-popup! :code-block-mode-picker
(code-block-mode-picker id format) {})
:select-code-block-mode
(open-editor-popup! :code-block-mode-picker
(code-block-mode-picker id format) {})
:template-search
(open-editor-popup! :template-search
(template-search id format) {})
:template-search
(open-editor-popup! :template-search
(template-search id format) {})
(:property-search :property-value-search)
(open-editor-popup! action
(if (= :property-search action)
(property-search id) (property-value-search id))
{})
(:property-search :property-value-search)
(open-editor-popup! action
(if (= :property-search action)
(property-search id) (property-value-search id))
{})
:zotero
(open-editor-popup! :zotero
(zotero/zotero-search id) {})
:zotero
(open-editor-popup! :zotero
(zotero/zotero-search id) {})
;; TODO: try remove local model state
false)]
#(when pid
(shui/popup-hide! pid))))
[action])
false)]
#(when pid
(shui/popup-hide! pid))))
[action])
[:<>])
(rum/defc command-popups <

View File

@@ -357,6 +357,7 @@
(.focus input))
(util/scroll-to (rum/deref *result-ref) 0 false))))]
[:div.cp__emoji-icon-picker
{:data-keep-selection true}
;; header
[:div.hd.bg-popover
(tab-observer @*tab {:reset-q! reset-q!})

View File

@@ -139,10 +139,13 @@
(defn- add-new-property-object!
[property set-data! properties]
(p/let [block (editor-handler/api-insert-new-block! ""
(p/let [default-value (if (= :checkbox (:logseq.property/type property))
false
(:db/id (db/entity :logseq.property/empty-placeholder)))
block (editor-handler/api-insert-new-block! ""
{:page (:block/uuid property)
:properties (merge
{(:db/ident property) (:db/id (db/entity :logseq.property/empty-placeholder))}
{(:db/ident property) default-value}
properties)
:edit-block? false})
_ (set-data! (get-property-related-objects (state/get-current-repo) property))]

View File

@@ -244,7 +244,7 @@
block' (if last-child-id (db/entity last-child-id) (last blocks))
link (:block/link block')]
(string/blank? (:block/title (or link block'))))))]
[:div
[:div.relative
{:class (when add-button? "show-add-button")}
(page-blocks-inner page-e blocks config sidebar? whiteboard? block-id)
(let [args (if block-id

View File

@@ -14,8 +14,8 @@
[frontend.db.async :as db-async]
[frontend.db.model :as db-model]
[frontend.handler.db-based.property :as db-property-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.notification :as notification]
[frontend.handler.property :as property-handler]
[frontend.handler.property.util :as pu]
[frontend.handler.route :as route-handler]
[frontend.hooks :as hooks]
@@ -116,6 +116,8 @@
(do
(shui/popup-hide!)
(shui/dialog-close!))
(pv/batch-operation?)
nil
(and block (= type :checkbox))
(p/do!
(ui/hide-popups-until-preview-popup!)
@@ -167,15 +169,18 @@
(map :block/title)
set))))
[])
(let [items (concat
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)}) properties)
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)
:group "Tags"}) classes))]
(let [items (->>
(concat
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)}) properties)
(map (fn [x]
{:label (:block/title x)
:value (:block/uuid x)
:group "Tags"}) classes))
(util/distinct-by-last-wins :value))]
[:div.ls-property-add.flex.flex-row.items-center.property-key
{:data-keep-selection true}
[:div.ls-property-key
(select/select (merge
{:items items
@@ -212,79 +217,87 @@
:size 15})))
(defn- property-input-on-chosen
[block *property *property-key *show-new-property-config? {:keys [class-schema?]}]
[block *property *property-key *show-new-property-config? {:keys [class-schema? remove-property?]}]
(fn [{:keys [value label]}]
(reset! *property-key (if (uuid? value) label value))
(let [property (when (uuid? value) (db/entity [:block/uuid value]))]
(when (and *show-new-property-config? (not (ldb/property? property)))
(reset! *show-new-property-config? true))
(reset! *property property)
(when property
(let [add-class-property? (and (ldb/class? block) class-schema?)
type (:logseq.property/type property)]
(cond
add-class-property?
(p/do!
(pv/<add-property! block (:db/ident property) "" {:class-schema? class-schema?})
(shui/popup-hide!)
(shui/dialog-close!))
(let [property (when (uuid? value) (db/entity [:block/uuid value]))
batch? (pv/batch-operation?)
repo (state/get-current-repo)]
(if (and property remove-property?)
(let [block-ids (map :block/uuid (pv/get-operating-blocks block))]
(property-handler/batch-remove-block-property! repo block-ids (:db/ident property))
(shui/popup-hide!))
(do
(when (and *show-new-property-config? (not (ldb/property? property)))
(reset! *show-new-property-config? true))
(reset! *property property)
(when property
(let [add-class-property? (and (ldb/class? block) class-schema?)
type (:logseq.property/type property)
default-or-url? (and (contains? #{:default :url} type)
(not (seq (:property/closed-values property))))]
(cond
add-class-property?
(p/do!
(pv/<add-property! block (:db/ident property) "" {:class-schema? class-schema?})
(shui/popup-hide!)
(shui/dialog-close!))
(= :checkbox type)
(p/do!
(ui/hide-popups-until-preview-popup!)
(shui/popup-hide!)
(shui/dialog-close!)
(let [value (if-some [value (:logseq.property/scalar-default-value property)]
value
false)]
(pv/<add-property! block (:db/ident property) value {:exit-edit? true})))
;; using class as property
(and property (ldb/class? property))
(p/do!
(pv/<set-class-as-property! (state/get-current-repo) property)
(reset! *show-new-property-config? false))
(and (contains? #{:default :url} type)
(not (seq (:property/closed-values property))))
(pv/<create-new-block! block property "")
(and batch? (or (= :checkbox type) (and batch? default-or-url?)))
nil
;; using class as property
(and property (ldb/class? property))
(pv/<set-class-as-property! (state/get-current-repo) property)
(= :checkbox type)
(p/do!
(ui/hide-popups-until-preview-popup!)
(shui/popup-hide!)
(shui/dialog-close!)
(let [value (if-some [value (:logseq.property/scalar-default-value property)]
value
false)]
(pv/<add-property! block (:db/ident property) value {:exit-edit? true})))
(or (not= :default type)
(and (= :default type) (seq (:property/closed-values property))))
(reset! *show-new-property-config? false)))))))
default-or-url?
(pv/<create-new-block! block property "" {:batch-op? true})
(or (not= :default type)
(and (= :default type) (seq (:property/closed-values property))))
(reset! *show-new-property-config? false)))))))))
(rum/defc property-key-title
[block property class-schema? property-position]
(let [block-container (state/get-component :block/container)]
(shui/trigger-as
:a
{:tabIndex 0
:title (:block/title property)
:class "property-k flex select-none jtrigger w-full"
:on-pointer-down (fn [^js e]
(when (util/meta-key? e)
(route-handler/redirect-to-page! (:block/uuid property))
(.preventDefault e)))
:on-click (fn [^js/MouseEvent e]
(if (state/editing?)
(editor-handler/escape-editing {:select? true})
(shui/popup-show! (.-target e)
(fn []
(property-config/dropdown-editor property block {:debug? (.-altKey e)
:class-schema? class-schema?}))
{:content-props
{:class "ls-property-dropdown-editor as-root"
:onEscapeKeyDown (fn [e]
(util/stop e)
(shui/popup-hide!)
(when-let [input (state/get-input)]
(.focus input)))}
:align "start"
:as-dropdown? true})))}
(if (= :block-below property-position)
(:block/title property)
(block-container {:property? true} property)))))
[block property class-schema?]
(shui/trigger-as
:a
{:tabIndex 0
:title (:block/title property)
:class "property-k flex select-none jtrigger w-full"
:on-pointer-down (fn [^js e]
(when (util/meta-key? e)
(route-handler/redirect-to-page! (:block/uuid property))
(.preventDefault e)))
:on-click (fn [^js/MouseEvent e]
(shui/popup-show! (.-target e)
(fn []
(property-config/dropdown-editor property block {:debug? (.-altKey e)
:class-schema? class-schema?}))
{:content-props
{:class "ls-property-dropdown-editor as-root"
:onEscapeKeyDown (fn [e]
(util/stop e)
(shui/popup-hide!)
(when-let [input (state/get-input)]
(.focus input)))}
:align "start"
:as-dropdown? true}))}
(:block/title property)))
(rum/defc property-key-cp < rum/static
[block property {:keys [other-position? class-schema? property-position]}]
[block property {:keys [other-position? class-schema?]}]
(let [icon (:logseq.property/icon property)]
[:div.property-key-inner.jtrigger-view
;; icon picker
@@ -319,10 +332,9 @@
[:a.property-k.flex.select-none.jtrigger
{:on-click #(route-handler/redirect-to-page! (:block/uuid property))}
(:block/title property)]
(property-key-title block property class-schema? property-position))]))
(property-key-title block property class-schema?))]))
(rum/defcs property-input < rum/reactive
(rum/local nil ::ref)
(rum/defcs ^:large-vars/cleanup-todo property-input < rum/reactive
(rum/local false ::show-new-property-config?)
(rum/local false ::show-class-select?)
(rum/local {} ::property-schema)
@@ -354,8 +366,7 @@
state)}
[state block *property-key {:keys [class-schema?]
:as opts}]
(let [*ref (::ref state)
*property (::property state)
(let [*property (::property state)
*show-new-property-config? (::show-new-property-config? state)
*show-class-select? (::show-class-select? state)
*property-schema (::property-schema state)
@@ -374,16 +385,24 @@
(and (not= view-context :all) (not (contains? block-types view-context)))
(and (ldb/built-in? block) (contains? #{:logseq.property/parent} (:db/ident m))))))
property (rum/react *property)
property-key (rum/react *property-key)]
property-key (rum/react *property-key)
batch? (pv/batch-operation?)
hide-property-key? (or (contains? #{:date :datetime} (:logseq.property/type property))
(pv/select-type? block property)
(and
batch?
(contains? #{:default :url} (:logseq.property/type property))
(not (seq (:property/closed-values property))))
(and property (ldb/class? property)))]
[:div.ls-property-input.flex.flex-1.flex-row.items-center.flex-wrap.gap-1
{:ref #(reset! *ref %)}
(if property-key
[:div.ls-property-add.gap-1.flex.flex-1.flex-row.items-center
[:div.flex.flex-row.items-center.property-key.gap-1
(when-not (:db/id property) (property-icon property (:logseq.property/type @*property-schema)))
(if (:db/id property) ; property exists already
(property-key-cp block property opts)
[:div property-key])]
(when-not hide-property-key?
[:div.flex.flex-row.items-center.property-key.gap-1
(when-not (:db/id property) (property-icon property (:logseq.property/type @*property-schema)))
(if (:db/id property) ; property exists already
(property-key-cp block property opts)
[:div property-key])])
[:div.flex.flex-row {:on-pointer-down (fn [e] (util/stop-propagation e))}
(when (not= @*show-new-property-config? :adding-property)
(cond
@@ -416,8 +435,9 @@
(= "" (.-value (.-target e))))
(util/stop e)
(shui/popup-hide!)))}]
(property-select exclude-properties {:on-chosen on-chosen
:input-opts input-opts})))]))
(property-select exclude-properties
(merge (:select-opts opts) {:on-chosen on-chosen
:input-opts input-opts}))))]))
(rum/defcs new-property < rum/reactive
[state block opts]

View File

@@ -68,13 +68,13 @@
}
.property-k {
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap leading-[26px];
.jtrigger {
&-id {
@apply left-[16px];
}
}
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap;
}
.property-value {
@@ -188,6 +188,10 @@
@apply flex items-center shrink;
}
&[data-type] {
@apply flex items-center;
}
&[data-type="datetime"] {
@apply whitespace-nowrap;
}
@@ -270,7 +274,7 @@ a.control-link {
.property-value-container {
@apply flex-1 shrink-0;
min-width: 100px;
min-height: 29px;
min-height: 28px;
}
.ls-page-properties .property-key, .ls-properties-area .property-key {
@@ -280,7 +284,7 @@ a.control-link {
.property-key {
/* Same height with one-line block container */
min-width: 160px;
min-height: 29px;
min-height: 28px;
h1.title, h2.title {
border-bottom: 0;
@@ -303,16 +307,14 @@ a.control-link {
}
.property-key-inner {
@apply flex flex-row items-start gap-1 relative;
@apply flex flex-row items-center gap-1 relative;
.property-icon {
@apply flex items-center;
height: 28px;
button {
margin-top: -1px;
}
height: 26px;
margin-top: 1px;
}
.editor-inner {
textarea {
field-sizing: content;

View File

@@ -83,58 +83,54 @@
(rum/defc class-select
[property {:keys [multiple-choices? disabled? default-open? no-class? on-hide]
:or {multiple-choices? true}}]
(let [*ref (rum/use-ref nil)]
(hooks/use-effect!
(fn []
(when default-open?
(some-> (rum/deref *ref)
(.click))))
[default-open?])
(let [schema-classes (:logseq.property/classes property)]
[:div.flex.flex-1.col-span-3
(let [content-fn
(fn [{:keys [id]}]
(let [toggle-fn #(do
(when (fn? on-hide) (on-hide))
(shui/popup-hide! id))
classes (model/get-all-readable-classes (state/get-current-repo) {:except-root-class? true})
options (map (fn [class]
{:label (:block/title class)
:value (:block/uuid class)})
classes)
options (if no-class?
(cons {:label "Skip choosing tag"
:value :no-tag}
options)
options)
opts {:items options
:input-default-placeholder (if multiple-choices? "Choose tags" "Choose tag")
:dropdown? false
:close-modal? false
:multiple-choices? multiple-choices?
:selected-choices (map :block/uuid schema-classes)
:extract-fn :label
:extract-chosen-fn :value
:show-new-when-not-exact-match? true
:input-opts {:on-key-down
(fn [e]
(case (util/ekey e)
"Escape"
(do
(util/stop e)
(toggle-fn))
nil))}
:on-chosen (fn [value select?]
(if (= value :no-tag)
(toggle-fn)
(p/let [result (<create-class-if-not-exists! value)
value' (or result value)
tx-data [[(if select? :db/add :db/retract) (:db/id property) :logseq.property/classes [:block/uuid value']]]
_ (db/transact! (state/get-current-repo) tx-data {:outliner-op :update-property})]
(when-not multiple-choices? (toggle-fn)))))}]
(let [*ref (rum/use-ref nil)
schema-classes (:logseq.property/classes property)]
[:div.flex.flex-1.col-span-3
(let [content-fn
(fn [{:keys [id]}]
(let [toggle-fn #(do
(when (fn? on-hide) (on-hide))
(shui/popup-hide! id))
classes (model/get-all-readable-classes (state/get-current-repo) {:except-root-class? true})
options (map (fn [class]
{:label (:block/title class)
:value (:block/uuid class)})
classes)
options (if no-class?
(cons {:label "Skip choosing tag"
:value :no-tag}
options)
options)
opts {:items options
:input-default-placeholder (if multiple-choices? "Choose tags" "Choose tag")
:dropdown? false
:close-modal? false
:multiple-choices? multiple-choices?
:selected-choices (map :block/uuid schema-classes)
:extract-fn :label
:extract-chosen-fn :value
:show-new-when-not-exact-match? true
:input-opts {:on-key-down
(fn [e]
(case (util/ekey e)
"Escape"
(do
(util/stop e)
(toggle-fn))
nil))}
:on-chosen (fn [value select?]
(if (= value :no-tag)
(toggle-fn)
(p/let [result (<create-class-if-not-exists! value)
value' (or result value)
tx-data [[(if select? :db/add :db/retract) (:db/id property) :logseq.property/classes [:block/uuid value']]]
_ (db/transact! (state/get-current-repo) tx-data {:outliner-op :update-property})]
(when-not multiple-choices? (toggle-fn)))))}]
(select/select opts)))]
(select/select opts)))]
(if default-open?
(content-fn nil)
[:div.flex.flex-1.cursor-pointer
{:ref *ref
:on-click (if disabled?
@@ -147,7 +143,7 @@
[:a.text-sm (str "#" (:block/title class))])
[:span.opacity-60.pl-1.top-1.relative.hover:opacity-80.active:opacity-60
(shui/tabler-icon "edit")]]
(pv/property-empty-btn-value property))])])))
(pv/property-empty-btn-value property))]))]))
(rum/defc name-edit-pane
[property {:keys [set-sub-open! disabled?]}]
@@ -326,12 +322,12 @@
:button-opts {:title "Set Icon"}})
[:strong {:on-click (fn [^js e]
(shui/popup-show! (.-target e)
(fn [] (choice-base-edit-form property block))
{:id :ls-base-edit-form
:align "start"}))}
(fn [] (choice-base-edit-form property block))
{:id :ls-base-edit-form
:align "start"}))}
value]
(shui/dropdown-menu
(shui/dropdown-menu-trigger
(shui/dropdown-menu-trigger
{:as-child true
:disabled disabled?}
(shui/button
@@ -409,24 +405,24 @@
[:<>
[:ul.choices-list
(dnd/items choice-items
{:sort-by-inner-element? false
:on-drag-end (fn [_ {:keys [active-id over-id direction]}]
(let [move-down? (= direction :down)
over (db/entity [:block/uuid (uuid over-id)])
active (db/entity [:block/uuid (uuid active-id)])
over-order (:block/order over)
new-order (if move-down?
(let [next-order (db-order/get-next-order (db/get-db) property (:db/id over))]
(db-order/gen-key over-order next-order))
(let [prev-order (db-order/get-prev-order (db/get-db) property (:db/id over))]
(db-order/gen-key prev-order over-order)))]
{:sort-by-inner-element? false
:on-drag-end (fn [_ {:keys [active-id over-id direction]}]
(let [move-down? (= direction :down)
over (db/entity [:block/uuid (uuid over-id)])
active (db/entity [:block/uuid (uuid active-id)])
over-order (:block/order over)
new-order (if move-down?
(let [next-order (db-order/get-next-order (db/get-db) property (:db/id over))]
(db-order/gen-key over-order next-order))
(let [prev-order (db-order/get-prev-order (db/get-db) property (:db/id over))]
(db-order/gen-key prev-order over-order)))]
(db/transact! (state/get-current-repo)
[{:db/id (:db/id active)
:block/order new-order}
(outliner-core/block-with-updated-at
{:db/id (:db/id property)})]
{:outliner-op :save-block})))})]
(db/transact! (state/get-current-repo)
[{:db/id (:db/id active)
:block/order new-order}
(outliner-core/block-with-updated-at
{:db/id (:db/id property)})]
{:outliner-op :save-block})))})]
(shui/dropdown-menu-separator)])
;; add choice
@@ -463,14 +459,14 @@
[choices]
(let [select-cp (fn [opts]
(shui/select
opts
(shui/select-trigger
{:class "h-8"}
(shui/select-value {:placeholder "Select a choice"}))
(shui/select-content
(map (fn [choice]
(shui/select-item {:key (str (:db/id choice))
:value (:db/id choice)} (:block/title choice))) choices))))
opts
(shui/select-trigger
{:class "h-8"}
(shui/select-value {:placeholder "Select a choice"}))
(shui/select-content
(map (fn [choice]
(shui/select-item {:key (str (:db/id choice))
:value (:db/id choice)} (:block/title choice))) choices))))
checked-choice (some (fn [choice] (when (true? (:logseq.property/choice-checkbox-state choice)) choice)) choices)
unchecked-choice (some (fn [choice] (when (false? (:logseq.property/choice-checkbox-state choice)) choice)) choices)]
[:div.flex.flex-col.gap-4.text-sm.p-2

View File

@@ -1,18 +1,27 @@
(ns frontend.components.property.dialog
"Property && value choose"
(:require [frontend.components.property :as property-component]
[rum.core :as rum]
[frontend.db :as db]
[frontend.modules.shortcut.core :as shortcut]
[frontend.db :as db]))
[frontend.state :as state]
[rum.core :as rum]))
(rum/defcs dialog <
shortcut/disable-all-shortcuts
(rum/local nil ::property-value)
{:init (fn [state]
(let [k (:property-key (last (:rum/args state)))]
(let [opts (last (:rum/args state))
k (:property-key opts)]
(when-let [view-selected-blocks (:selected-blocks opts)]
(state/set-state! :view/selected-blocks view-selected-blocks))
(assoc state
::property-key (atom k)
::property (atom (when k (db/get-case-page k))))))}
::property (atom (when k (db/get-case-page k))))))
:will-unmount (fn [state]
(when-let [close-fn (:on-dialog-close (last (:rum/args state)))]
(close-fn))
(state/set-state! :view/selected-blocks nil)
state)}
[state blocks opts]
(when (seq blocks)
(let [*property-key (::property-key state)

View File

@@ -18,6 +18,7 @@
[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.notification :as notification]
[frontend.handler.page :as page-handler]
[frontend.handler.property :as property-handler]
[frontend.handler.property.util :as pu]
@@ -64,6 +65,29 @@
(ui/icon "line-dashed"))
"Empty")])
(defn- get-selected-blocks
[]
(some->> (state/get-selection-block-ids)
(map (fn [id] (db/entity [:block/uuid id])))
(seq)
block-handler/get-top-level-blocks
(remove ldb/property?)))
(defn get-operating-blocks
[block]
(let [selected-blocks (get-selected-blocks)
view-selected-blocks (:view/selected-blocks @state/state)]
(or (seq selected-blocks)
(seq view-selected-blocks)
[block])))
(defn batch-operation?
[]
(let [selected-blocks (get-selected-blocks)
view-selected-blocks (:view/selected-blocks @state/state)]
(or (> (count selected-blocks) 1)
(seq view-selected-blocks))))
(rum/defc icon-row
[block editing?]
(let [icon-value (:logseq.property/icon block)
@@ -71,14 +95,13 @@
(shui/dialog-close!)
(shui/popup-hide-all!))
on-chosen! (fn [_e icon]
(if icon
(db-property-handler/set-block-property!
(:db/id block)
(let [repo (state/get-current-repo)
blocks (get-operating-blocks block)]
(property-handler/batch-set-block-property!
repo
(map :db/id blocks)
:logseq.property/icon
(select-keys icon [:type :id :color]))
(db-property-handler/remove-block-property!
(:db/id block)
:logseq.property/icon))
(when icon (select-keys icon [:type :id :color]))))
(clear-overlay!)
(when editing?
(editor-handler/restore-last-saved-cursor!)))]
@@ -96,14 +119,14 @@
(.querySelector ".block-main-container"))]
(state/set-editor-action! :property-icon-picker)
(shui/popup-show! target
#(icon-component/icon-search
{:on-chosen on-chosen!
:icon-value icon
:del-btn? (some? icon)})
{:id :ls-icon-picker
:on-after-hide #(state/set-editor-action! nil)
:content-props {:onEscapeKeyDown #(when editing? (editor-handler/restore-last-saved-cursor!))}
:align :start})))))))
#(icon-component/icon-search
{:on-chosen on-chosen!
:icon-value icon
:del-btn? (some? icon)})
{:id :ls-icon-picker
:on-after-hide #(state/set-editor-action! nil)
:content-props {:onEscapeKeyDown #(when editing? (editor-handler/restore-last-saved-cursor!))}
:align :start})))))))
[editing?])
[:div.col-span-3.flex.flex-row.items-center.gap-2
@@ -112,22 +135,14 @@
:del-btn? (some? icon-value)
:on-chosen on-chosen!})]))
(defn- select-type?
[block property type]
(or (contains? #{:node :number :date :page :class :property} type)
;; closed values
(seq (:property/closed-values property))
(and (= (:db/ident property) :logseq.property/default-value)
(= (:logseq.property/type block) :number))))
(defn- get-operating-blocks
[block]
(let [selected-blocks (some->> (state/get-selection-block-ids)
(map (fn [id] (db/entity [:block/uuid id])))
(seq)
block-handler/get-top-level-blocks
(remove ldb/property?))]
(or (seq selected-blocks) [block])))
(defn select-type?
[block property]
(let [type (:logseq.property/type property)]
(or (contains? #{:node :number :date :page :class :property} type)
;; closed values
(seq (:property/closed-values property))
(and (= (:db/ident property) :logseq.property/default-value)
(= (:logseq.property/type block) :number)))))
(defn <create-new-block!
[block property value & {:keys [edit-block? batch-op?]
@@ -139,22 +154,17 @@
(let [<create-block (fn [block]
(if (and (contains? #{:default :url} (:logseq.property/type property))
(not (db-property/many? property)))
(p/let [existing-value (get block (:db/ident property))
default-value (:logseq.property/default-value property)
existing-value? (and (some? existing-value)
(not= (:db/ident existing-value) :logseq.property/empty-placeholder)
(not= (:db/id existing-value) (:db/id default-value)))
new-block-id (when-not existing-value? (db/new-block-id))
_ (when-not existing-value?
(let [value' (if (and default-value (string? value) (string/blank? value))
(db-property/property-value-content default-value)
value)]
(db-property-handler/create-property-text-block!
(:db/id block)
(:db/id property)
value'
{:new-block-id new-block-id})))]
(if existing-value? existing-value (db/entity [:block/uuid new-block-id])))
(p/let [default-value (:logseq.property/default-value property)
new-block-id (db/new-block-id)
_ (let [value' (if (and default-value (string? value) (string/blank? value))
(db-property/property-value-content default-value)
value)]
(db-property-handler/create-property-text-block!
(:db/id block)
(:db/id property)
value'
{:new-block-id new-block-id}))]
(db/entity [:block/uuid new-block-id]))
(p/let [new-block-id (db/new-block-id)
_ (db-property-handler/create-property-text-block!
(:db/id block)
@@ -187,7 +197,7 @@
(defn <add-property!
"If a class and in a class schema context, add the property to its schema.
Otherwise, add a block's property and its value"
([block property-key property-value] (<add-property! block property-key property-value {}))
([block property-id property-value] (<add-property! block property-id property-value {}))
([block property-id property-value {:keys [selected? exit-edit? class-schema?]
:or {exit-edit? true}}]
(let [repo (state/get-current-repo)
@@ -204,20 +214,17 @@
(<set-class-as-property! repo property))
(db-property-handler/class-add-property! (:db/id block) property-id))
(let [block-ids (map :block/uuid blocks)]
(if (and (db-property-type/all-ref-property-types (:logseq.property/type property))
(string? property-value))
(p/let [new-block (<create-new-block! block (db/entity property-id) property-value {:edit-block? false})]
(when (seq (remove #{(:db/id block)} (map :db/id block)))
(property-handler/batch-set-block-property! repo block-ids property-id (:db/id new-block)))
new-block)
(property-handler/batch-set-block-property! repo block-ids property-id property-value))))
(cond
exit-edit?
(do
(ui/hide-popups-until-preview-popup!)
(shui/dialog-close!))
selected?
(shui/popup-hide!))
(property-handler/batch-set-block-property! repo block-ids property-id property-value)))
(when (seq (:view/selected-blocks @state/state))
(notification/show! "Property updated!" :success))
(when-not many?
(cond
exit-edit?
(do
(ui/hide-popups-until-preview-popup!)
(shui/dialog-close!))
selected?
(shui/popup-hide!)))
(when-not (or many? checkbox?)
(when-let [input (state/get-input)]
(.focus input)))
@@ -439,8 +446,7 @@
(rum/defc date-picker
[value {:keys [block property datetime? on-change on-delete del-btn? editing? multiple-values? other-position?]}]
(let [*trigger-ref (rum/use-ref nil)
content-fn (fn [{:keys [id]}] (calendar-inner id
(let [content-fn (fn [{:keys [id]}] (calendar-inner id
{:block block
:property property
:on-change on-change
@@ -456,52 +462,44 @@
(shui/popup-show! (.-target e) content-fn
{:align "start" :auto-focus? true}))))
repeated-task? (:logseq.task/repeated? block)]
(hooks/use-effect!
(fn []
(when editing?
(js/setTimeout
#(some-> (rum/deref *trigger-ref)
(.click)) 32)))
[editing?])
(if editing?
(content-fn {:id :date-picker})
(if multiple-values?
(shui/button
{:class "jtrigger h-6 empty-btn"
:variant :text
:size :sm
:on-click open-popup!}
(ui/icon "calendar-plus" {:size 16}))
(shui/trigger-as
:div.flex.flex-1.flex-row.gap-1.items-center.flex-wrap
{:tabIndex 0
:class "jtrigger min-h-[24px]" ; FIXME: min-h-6 not works
:on-click open-popup!}
[:div.flex.flex-row.gap-1.items-center
(when repeated-task?
(ui/icon "repeat" {:size 14 :class "opacity-40"}))
(cond
(map? value)
(let [date (tc/to-date-time (date/journal-day->utc-ms (:block/journal-day value)))
compare-value (some-> date
(t/plus (t/days 1))
(t/minus (t/seconds 1)))
content (when-let [page-cp (state/get-component :block/page-cp)]
(rum/with-key
(page-cp {:disable-preview? true
:meta-click? other-position?
:label (human-date-label (t/to-default-time-zone date))} value)
(:db/id value)))]
(if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} (:db/id property)))
(overdue compare-value content)
content))
(if multiple-values?
(shui/button
{:class "jtrigger h-6 empty-btn"
:ref *trigger-ref
:variant :text
:size :sm
:on-click open-popup!}
(ui/icon "calendar-plus" {:size 16}))
(shui/trigger-as
:div.flex.flex-1.flex-row.gap-1.items-center.flex-wrap
{:tabIndex 0
:class "jtrigger min-h-[24px]" ; FIXME: min-h-6 not works
:ref *trigger-ref
:on-click open-popup!}
[:div.flex.flex-row.gap-1.items-center
(when repeated-task?
(ui/icon "repeat" {:size 14 :class "opacity-40"}))
(cond
(map? value)
(let [date (tc/to-date-time (date/journal-day->utc-ms (:block/journal-day value)))
compare-value (some-> date
(t/plus (t/days 1))
(t/minus (t/seconds 1)))
content (when-let [page-cp (state/get-component :block/page-cp)]
(rum/with-key
(page-cp {:disable-preview? true
:meta-click? other-position?
:label (human-date-label (t/to-default-time-zone date))} value)
(:db/id value)))]
(if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} (:db/id property)))
(overdue compare-value content)
content))
(number? value)
(datetime-value value (:db/ident property) repeated-task?)
(number? value)
(datetime-value value (:db/ident property) repeated-task?)
:else
(property-empty-btn-value nil))]))))
:else
(property-empty-btn-value nil))])))))
(rum/defc property-value-date-picker
[block property value opts]
@@ -578,8 +576,8 @@
(remove nil?)
(remove #(= :logseq.property/empty-placeholder %)))
clear-value (str "No " (:block/title property))
clear-value-label [:div.flex.flex-row.items-center.gap-2
(ui/icon "x")
clear-value-label [:div.flex.flex-row.items-center.gap-1.text-sm
(ui/icon "x" {:size 14})
[:div clear-value]]
items' (->>
(if (and (seq selected-choices)
@@ -685,10 +683,17 @@
(and alias? (= (or (:db/id (:block/page block))
(:db/id block))
(:db/id node)))
(when (and property-type (not= property-type :node))
(cond
(= property-type :class)
(ldb/private-tags (:db/ident node))
(and property-type (not= property-type :node))
(if (= property-type :page)
(not (db/page? node))
(not (contains? (ldb/get-entity-types node) property-type))))))
(not (contains? (ldb/get-entity-types node) property-type)))
:else
false)))
result))))
options (map (fn [node]
@@ -731,13 +736,11 @@
"Set tags"
alias?
"Set alias"
multiple-choices?
"Choose nodes"
:else
"Choose node")
(str "Set " (:block/title property)))
:show-new-when-not-exact-match? (if (or (and parent-property? (contains? (set children-pages) (:db/id block)))
;; Don't allow creating private tags
(seq (set/intersection (set (map :db/ident classes))
(seq (set/intersection (set (map :db/ident classes'))
ldb/private-tags)))
false
true)
@@ -801,7 +804,10 @@
(set-result! result)))))
repo (state/get-current-repo)
classes (:logseq.property/classes property)
non-root-classes (remove (fn [c] (= (:db/ident c) :logseq.class/Root)) classes)
class? (= :class (:logseq.property/type property))
non-root-classes (cond-> (remove (fn [c] (= (:db/ident c) :logseq.class/Root)) classes)
class?
(conj (frontend.db/entity :logseq.class/Tag)))
parent-property? (= (:db/ident property) :logseq.property/parent)]
(when (and (not parent-property?) (seq non-root-classes))
;; effect runs once
@@ -891,7 +897,7 @@
:selected-choices selected-choices
:dropdown? dropdown?
:show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
:input-default-placeholder "Select"
:input-default-placeholder (str "Set " (:block/title property))
:extract-chosen-fn :value
:extract-fn (fn [x] (or (:label-value x) (:label x)))
:content-props content-props
@@ -1044,49 +1050,46 @@
(inline-text-cp (str value)))]))
(rum/defc single-value-select
[block property value value-f select-opts opts]
(let [*el (rum/use-ref nil)]
;; Open popover initially when editing a property
(hooks/use-effect!
(fn []
(when (:editing? opts)
(.click (rum/deref *el))))
[(:editing? opts)])
(let [type (:logseq.property/type property)
select-opts' (assoc select-opts :multiple-choices? false)
popup-content (fn content-fn [_]
[:div.property-select
(case type
(:entity :number :default :url)
(select block property select-opts' opts)
[block property value select-opts {:keys [value-render] :as opts}]
(let [*el (rum/use-ref nil)
editing? (:editing? opts)
type (:logseq.property/type property)
select-opts' (assoc select-opts :multiple-choices? false)
popup-content (fn content-fn [_]
[:div.property-select
(case type
(:entity :number :default :url)
(select block property select-opts' opts)
(:node :class :property :page :date)
(property-value-select-node block property select-opts' opts))])
trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
show! (fn [e]
(let [target (.-target e)]
(when-not (or config/publishing?
(util/shift-key? e)
(util/meta-key? e)
(util/link? target)
(when-let [node (.closest target "a")]
(not (or (d/has-class? node "page-ref")
(d/has-class? node "tag")))))
(:node :class :property :page :date)
(property-value-select-node block property select-opts' opts))])]
(if editing?
(popup-content nil)
(let [trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
show! (fn [e]
(let [target (.-target e)]
(when-not (or config/publishing?
(util/shift-key? e)
(util/meta-key? e)
(util/link? target)
(when-let [node (.closest target "a")]
(not (or (d/has-class? node "page-ref")
(d/has-class? node "tag")))))
(shui/popup-show! target popup-content
{:align "start"
:as-dropdown? true
:auto-focus? true
:trigger-id trigger-id}))))]
(shui/trigger-as
(if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full)
{:ref *el
:id trigger-id
:tabIndex 0
:on-click show!}
(if (string/blank? value)
(property-empty-text-value property opts)
(value-f))))))
(shui/popup-show! target popup-content
{:align "start"
:as-dropdown? true
:auto-focus? true
:trigger-id trigger-id}))))]
(shui/trigger-as
(if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full)
{:ref *el
:id trigger-id
:tabIndex 0
:on-click show!}
(if (string/blank? value)
(property-empty-text-value property opts)
(value-render)))))))
(defn- property-value-inner
[block property value {:keys [inline-text page-cp
@@ -1125,8 +1128,10 @@
editing? (or editing?
(and (state/sub-editing? [container-id (:block/uuid block)])
(= (:db/id property) (:db/id (:property (state/get-editor-action-data))))))
select-type?' (select-type? block property type)
batch? (batch-operation?)
closed-values? (seq (:property/closed-values property))
select-type?' (or (select-type? block property)
(and editing? batch? (contains? #{:default :url} type) (not closed-values?)))
select-opts {:on-chosen on-chosen}
value (if (and (de/entity? value*) (= (:db/ident value*) :logseq.property/empty-placeholder))
nil
@@ -1154,9 +1159,10 @@
(when choice
(db-property-handler/set-block-property! (:db/id block) (:db/ident property) (:db/id choice)))))}))
(single-value-select block property value
(fn [] (select-item property type value opts))
select-opts
(assoc opts :editing? editing?))))
(assoc opts
:editing? editing?
:value-render (fn [] (select-item property type value opts))))))
(case type
(:date :datetime)
(property-value-date-picker block property value (merge opts {:editing? editing?}))
@@ -1187,24 +1193,22 @@
*el (rum/use-ref nil)
items (cond->> (if (de/entity? v) #{v} v)
(= (:db/ident property) :block/tags)
(remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))]
(hooks/use-effect!
(fn []
(when editing?
(.click (rum/deref *el))))
[editing?])
(let [select-cp (fn [select-opts]
(let [select-opts (merge {:multiple-choices? true
:on-chosen (fn []
(when on-chosen (on-chosen)))}
select-opts
{:dropdown? false})]
[:div.property-select
(if (contains? #{:node :page :class :property} type)
(property-value-select-node block property
select-opts
opts)
(select block property select-opts opts))]))]
(remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))
select-cp (fn [select-opts]
(let [select-opts (merge {:multiple-choices? true
:on-chosen (fn []
(when on-chosen (on-chosen)))}
select-opts
(when-not editing?
{:dropdown? false}))]
[:div.property-select
(if (contains? #{:node :page :class :property} type)
(property-value-select-node block property
select-opts
opts)
(select block property select-opts opts))]))]
(if editing?
(select-cp {})
(let [toggle-fn shui/popup-hide!
content-fn (fn [{:keys [_id content-props]}]
(select-cp {:content-props content-props}))]
@@ -1223,7 +1227,7 @@
(do (some-> (rum/deref *el) (.click))
(util/stop e))
:dune))
:class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2"}
:class "flex flex-1 flex-row items-center flex-wrap gap-1"}
(let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
(if (and (seq items) not-empty-value?)
(concat
@@ -1244,8 +1248,8 @@
(when (some? value) #{value}))]
(multiple-values-inner block property value' opts)))
(rum/defcs property-value < rum/reactive db-mixins/query
[state block property {:keys [show-tooltip? p-block p-property]
(rum/defcs ^:large-vars/cleanup-todo property-value < rum/reactive db-mixins/query
[state block property {:keys [show-tooltip? p-block p-property editing?]
:as opts}]
(ui/catch-error
(ui/block-error "Something wrong" {})
@@ -1303,7 +1307,7 @@
(properties-cp {} block {:selected? false
:class-schema? true})
(and multiple-values? (contains? #{:default :url} type) (not closed-values?))
(and multiple-values? (contains? #{:default :url} type) (not closed-values?) (not editing?))
(property-normal-block-value block property v)
multiple-values?
@@ -1339,7 +1343,9 @@
(shui/tooltip
{:delayDuration 1200}
(shui/tooltip-trigger
{:onFocusCapture #(util/stop-propagation %)} value-cp)
{:onFocusCapture #(util/stop-propagation %)
:as-child true}
value-cp)
(shui/tooltip-content
(str "Change " (:block/title property)))))
value-cp))))))

View File

@@ -27,3 +27,7 @@
@apply text-yellow-rx-08 dark:text-yellow-rx-10;
}
}
.ls-property-key .cp__select-input, .property-select .cp__select-input {
@apply text-sm;
}

View File

@@ -2,12 +2,12 @@
"RTC state indicator"
(:require [cljs-time.core :as t]
[clojure.pprint :as pprint]
[frontend.common.missionary :as c.m]
[frontend.db :as db]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[frontend.common.missionary :as c.m]
[logseq.shui.ui :as shui]
[missionary.core :as m]
[rum.core :as rum]))
@@ -51,7 +51,7 @@
:local-tx (:local-tx state)
:remote-tx (:remote-tx state)
:rtc-state (if (:rtc-lock state) :open :close)))
rtc-flows/rtc-state-flow))
rtc-flows/rtc-state-stream-flow))
::update-detail-info)]
(reset! *update-detail-info-canceler canceler))))
(run-task--update-detail-info)

View File

@@ -3,20 +3,20 @@
select-config to add a new use or select-type for this component. To use the
new select-type, create an event that calls `select/dialog-select!` with the
select-type. See the :graph/open command for a full example."
(:require [frontend.modules.shortcut.core :as shortcut]
(:require [clojure.string :as string]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.handler.common.developer :as dev-common-handler]
[frontend.handler.repo :as repo-handler]
[frontend.modules.shortcut.core :as shortcut]
[frontend.search :as search]
[frontend.state :as state]
[frontend.ui :as ui]
[logseq.shui.ui :as shui]
[frontend.util :as util]
[frontend.util.text :as text-util]
[rum.core :as rum]
[frontend.config :as config]
[frontend.handler.repo :as repo-handler]
[frontend.handler.common.developer :as dev-common-handler]
[logseq.shui.ui :as shui]
[reitit.frontend.easy :as rfe]
[clojure.string :as string]))
[rum.core :as rum]))
(rum/defc render-item < rum/reactive
[result chosen? multiple-choices? *selected-choices]
@@ -24,7 +24,9 @@
(:value result)) result)
header (:header result)
selected-choices (rum/react *selected-choices)
row [:div.flex.flex-row.justify-between.w-full {:class (when chosen? "chosen")}
row [:div.flex.flex-row.justify-between.w-full
{:class (when chosen? "chosen")
:on-pointer-down util/stop-propagation}
[:div.flex.flex-row.items-center.gap-1
(when multiple-choices?
(ui/checkbox {:checked (boolean (selected-choices (:value result)))

View File

@@ -0,0 +1,87 @@
(ns frontend.components.selection
"Block selection"
(:require [frontend.config :as config]
[frontend.handler.editor :as editor-handler]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.util :as util]
[logseq.shui.ui :as shui]
[rum.core :as rum]))
(rum/defc action-bar
[& {:keys [on-cut on-copy selected-blocks hide-dots? button-border?]
:or {on-cut #(editor-handler/cut-selection-blocks true)}}]
(let [on-copy (if (and selected-blocks (nil? on-copy))
#(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks
:page-title-only? true})
(or on-copy #(editor-handler/copy-selection-blocks true)))
button-opts {:variant :outline
:size :sm
:class (str "p-2 text-xs h-8"
(when-not button-border?
" !border-b-0"))}
db-graph? (config/db-based-graph?)]
[:div.selection-action-bar
(shui/button-group
;; set tag
(when db-graph?
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(state/pub-event! [:editor/new-property {:target (.-target e)
:selected-blocks selected-blocks
:property-key "Tags"
:on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
(ui/tooltip (ui/icon "hash" {:size 13}) "Set tag"
{:trigger-props {:class "flex"}})))
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(on-copy)
(state/pub-event! [:editor/hide-action-bar])))
"Copy")
(when db-graph?
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(state/pub-event! [:editor/new-property {:target (.-target e)
:selected-blocks selected-blocks
:on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
"Set property"))
(when db-graph?
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(state/pub-event! [:editor/new-property {:target (.-target e)
:selected-blocks selected-blocks
:remove-property? true
:select-opts {:show-new-when-not-exact-match? false}
:on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
"Unset property"))
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(on-cut)
(state/pub-event! [:editor/hide-action-bar])))
(ui/icon "trash" {:size 13}))
(when-not hide-dots?
(shui/button
(assoc button-opts
:on-pointer-down (fn [e]
(util/stop e)
(shui/popup-hide!)
(shui/popup-show! e
(fn [{:keys [id]}]
[:div {:on-click #(shui/popup-hide! id)
:data-keep-selection true}
((state/get-component :selection/context-menu))])
{:on-before-hide state/dom-clear-selection!
:on-after-hide state/state-clear-selection!
:content-props {:class "w-[280px] ls-context-menu-content"}
:as-dropdown? true})))
(ui/icon "dots" {:size 13}))))]))

View File

@@ -13,6 +13,7 @@
[frontend.components.property.config :as property-config]
[frontend.components.property.value :as pv]
[frontend.components.select :as select]
[frontend.components.selection :as selection]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
@@ -408,18 +409,12 @@
[table selected-rows {:keys [on-delete-rows]}]
(shui/table-actions
{}
(shui/button
{:variant "ghost"
:class "h-8 !pl-4 !px-2 !py-0 hover:text-foreground w-full justify-start"
:disabled true}
(str (count selected-rows) " selected"))
(when (fn? on-delete-rows)
(shui/button
{:variant "ghost"
:class "h-8 !pl-0 !px-2 !py-0 text-muted-foreground hover:text-foreground w-full justify-start"
:on-click (fn []
(on-delete-rows table selected-rows))}
(ui/icon "trash")))))
[:div (str (count selected-rows) " selected")]
(selection/action-bar
{:on-cut #(on-delete-rows table selected-rows)
:selected-blocks selected-rows
:hide-dots? true
:button-border? true})))
(rum/defc column-resizer
[_column on-sized!]
@@ -1580,8 +1575,9 @@
(search input {:on-change set-input!
:set-input! set-input!})
[:div.text-muted-foreground.text-sm
(pv/property-value view-entity (db/entity :logseq.property.view/type) {})]
(when db-based?
[:div.text-muted-foreground.text-sm
(pv/property-value view-entity (db/entity :logseq.property.view/type) {})])
(when db-based? (more-actions view-entity columns table))

View File

@@ -32,3 +32,13 @@
(m/eduction
(dedupe)
(m/watch *current-login-user)))
(def document-visibility-state-flow
(->> (m/observe
(fn ctor [emit!]
(let [callback-fn #(emit! js/document.visibilityState)]
(.addEventListener ^js js/document "visibilitychange" callback-fn)
(callback-fn)
(fn dtor [] (.removeEventListener ^js js/document "visibilitychange" callback-fn)))))
(m/eduction (dedupe))
(m/relieve)))

View File

@@ -5,6 +5,7 @@
[electron.ipc :as ipc]
[electron.listener :as el]
[frontend.components.block :as block]
[frontend.components.content :as cp-content]
[frontend.components.editor :as editor]
[frontend.components.page :as page]
[frontend.components.reference :as reference]
@@ -16,8 +17,8 @@
[frontend.error :as error]
[frontend.handler.command-palette :as command-palette]
[frontend.handler.events :as events]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.file-based.events]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.global-config :as global-config-handler]
[frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
@@ -127,6 +128,7 @@
(state/set-component! :block/inline-text block/inline-text)
(state/set-component! :block/asset-cp block/asset-cp)
(state/set-component! :editor/box editor/box)
(state/set-component! :selection/context-menu cp-content/custom-context-menu-content)
(command-palette/register-global-shortcut-commands))
(defn- get-system-info

View File

@@ -71,13 +71,13 @@
result (ui-outliner-tx/transact!
{:outliner-op :create-page}
(outliner-op/create-page! title' options'))
[_page-name page-uuid] (ldb/read-transit-str result)]
[_page-name page-uuid] (ldb/read-transit-str result)
page (db/get-page (or page-uuid title'))]
(when redirect?
(route-handler/redirect-to-page! page-uuid)
(let [page (db/get-page (or page-uuid title'))]
(when-let [first-block (ldb/get-first-child @conn (:db/id page))]
(block-handler/edit-block! first-block :max {:container-id :unknown-container}))
page)))))))))
(when-let [first-block (ldb/get-first-child @conn (:db/id page))]
(block-handler/edit-block! first-block :max {:container-id :unknown-container})))
page)))))))
;; favorite fns
;; ============

View File

@@ -4,7 +4,8 @@
[frontend.flows :as flows]
[frontend.state :as state]
[logseq.common.util :as common-util]
[missionary.core :as m]))
[missionary.core :as m])
(:import [missionary Cancelled]))
(def rtc-log-flow
(m/watch (:rtc/log @state/state)))
@@ -25,7 +26,10 @@
rtc-log-flow))
(def rtc-state-flow
(m/stream (m/watch (:rtc/state @state/state))))
(m/watch (:rtc/state @state/state)))
(def rtc-state-stream-flow
(m/stream rtc-state-flow))
(def rtc-online-users-flow
(c.m/throttle
@@ -36,7 +40,7 @@
(:rtc-lock m))
(:online-users m))))
(dedupe)
rtc-state-flow)))
rtc-state-stream-flow)))
(def ^:private network-online-change-flow
(m/stream
@@ -60,7 +64,7 @@ conditions:
(->> (m/latest
(fn [rtc-state _ login-user]
(assoc rtc-state :login-user login-user))
(c.m/continue-flow rtc-state-flow)
(c.m/continue-flow rtc-state-stream-flow)
(c.m/continue-flow network-online-change-flow)
flows/current-login-user-flow)
(m/eduction
@@ -89,6 +93,19 @@ conditions:
(assert (some? repo))
(reset! *rtc-start-trigger repo))
(def ^:private document-visible&rtc-not-running-flow
(m/ap
(let [visibility (m/?< flows/document-visibility-state-flow)]
(try
(if (= "visible" visibility)
(let [rtc-lock (:rtc-lock (m/? (m/reduce {} nil (m/eduction (take 1) rtc-state-flow))))]
(if (not rtc-lock)
:document-visible&rtc-not-running
(m/amb)))
(m/amb))
(catch Cancelled _
(m/amb))))))
(def trigger-start-rtc-flow
(->>
[(m/eduction
@@ -99,7 +116,10 @@ conditions:
flows/current-repo-flow)
(m/eduction
(keep (fn [repo] (when repo [:trigger-rtc repo])))
(m/watch *rtc-start-trigger))]
(m/watch *rtc-start-trigger))
(m/eduction
(map vector)
document-visible&rtc-not-running-flow)]
(apply c.m/mix)
(m/eduction (filter (fn [_] (some? (state/get-auth-id-token)))))
(c.m/debounce 200)))

View File

@@ -924,14 +924,16 @@
(block-handler/select-block! block-uuid))
(defn- compose-copied-blocks-contents
[repo block-ids]
[repo block-ids & {:as opts}]
(let [blocks (db-utils/pull-many repo '[*] (mapv (fn [id] [:block/uuid id]) block-ids))
top-level-block-uuids (->> (block-handler/get-top-level-blocks blocks)
(map :block/uuid))
content (export-text/export-blocks-as-markdown
repo top-level-block-uuids
{:indent-style (state/get-export-block-text-indent-style)
:remove-options (set (state/get-export-block-text-remove-options))})]
(merge
opts
{:indent-style (state/get-export-block-text-indent-style)
:remove-options (set (state/get-export-block-text-remove-options))}))]
[top-level-block-uuids content]))
(defn- get-all-blocks-by-ids
@@ -947,35 +949,37 @@
result)))
(defn copy-selection-blocks
[html?]
(when-let [blocks (seq (state/get-selection-blocks))]
(let [repo (state/get-current-repo)
ids (distinct (keep #(when-let [id (dom/attr % "blockid")]
[html? & {:keys [selected-blocks] :as opts}]
(let [repo (state/get-current-repo)
blocks (seq (state/get-selection-blocks))
ids (if blocks
(distinct (keep #(when-let [id (dom/attr % "blockid")]
(uuid id)) blocks))
[top-level-block-uuids content] (compose-copied-blocks-contents repo ids)
block (db/entity [:block/uuid (first ids)])
db-based? (config/db-based-graph? repo)]
(when block
(let [html (export-html/export-blocks-as-html repo top-level-block-uuids nil)
copied-blocks (cond->> (get-all-blocks-by-ids repo top-level-block-uuids)
db-based?
(map (fn [block]
(let [b (db/entity (:db/id block))]
(->
(->> (map (fn [[k v]]
(let [v' (cond
(and (map? v) (:db/id v))
[:block/uuid (:block/uuid (db/entity (:db/id v)))]
(and (coll? v) (every? #(and (map? %) (:db/id %)) v))
(set (map (fn [i] [:block/uuid (:block/uuid (db/entity (:db/id i)))]) v))
:else
v)]
[k v'])) b)
(into {}))
(assoc :db/id (:db/id b)))))))]
(common-handler/copy-to-clipboard-without-id-property! repo (get block :block/format :markdown) content (when html? html) copied-blocks))
(state/set-block-op-type! :copy)
(notification/show! "Copied!" :success)))))
(map :block/uuid selected-blocks))
[top-level-block-uuids content] (compose-copied-blocks-contents repo ids opts)
block (db/entity [:block/uuid (first ids)])
db-based? (config/db-based-graph? repo)]
(when block
(let [html (export-html/export-blocks-as-html repo top-level-block-uuids nil)
copied-blocks (cond->> (get-all-blocks-by-ids repo top-level-block-uuids)
db-based?
(map (fn [block]
(let [b (db/entity (:db/id block))]
(->
(->> (map (fn [[k v]]
(let [v' (cond
(and (map? v) (:db/id v))
[:block/uuid (:block/uuid (db/entity (:db/id v)))]
(and (coll? v) (every? #(and (map? %) (:db/id %)) v))
(set (map (fn [i] [:block/uuid (:block/uuid (db/entity (:db/id i)))]) v))
:else
v)]
[k v'])) b)
(into {}))
(assoc :db/id (:db/id b)))))))]
(common-handler/copy-to-clipboard-without-id-property! repo (get block :block/format :markdown) content (when html? html) copied-blocks))
(state/set-block-op-type! :copy)
(notification/show! "Copied!" :success))))
(defn copy-block-refs
[]
@@ -1236,6 +1240,18 @@
(state/conj-selection-block! blocks direction)))
(state/exit-editing-and-set-selected-blocks! blocks direction))))))))
(defonce *action-bar-timeout (atom nil))
(defn show-action-bar!
[& {:keys [delay]
:or {delay 200}}]
(when (config/db-based-graph?)
(when-let [timeout @*action-bar-timeout]
(js/clearTimeout timeout))
(state/pub-event! [:editor/hide-action-bar])
(let [timeout (js/setTimeout #(state/pub-event! [:editor/show-action-bar]) delay)]
(reset! *action-bar-timeout timeout))))
(defn- select-block-up-down
[direction]
(cond
@@ -1273,6 +1289,7 @@
(when element
(util/scroll-to-block element)
(state/drop-last-selection-block!))))
(show-action-bar! {:delay 500})
nil)
(defn on-select-block
@@ -2638,7 +2655,7 @@
(keydown-new-line))))))
(defn- select-first-last
"Select first or last block in viewpoint"
"Select first or last block in viewport"
[direction]
(let [f (case direction :up last :down first)
container (if (some-> js/document.activeElement
@@ -3375,6 +3392,7 @@
(defn shortcut-up-down [direction]
(fn [e]
(state/pub-event! [:editor/hide-action-bar])
(when (and (not (auto-complete?))
(or (in-page-preview?)
(not (in-shui-popup?)))
@@ -3414,10 +3432,14 @@
(cursor/select-up-down input direction anchor cursor-rect)))
(select-block-up-down direction))))
(defn popup-exists?
[id]
(some->> (shui-popup/get-popups)
(some #(some-> % (:id) (str) (string/includes? (str id))))))
(defn editor-commands-popup-exists?
[]
(some->> (shui-popup/get-popups)
(some #(some-> % (:id) (str) (string/starts-with? ":editor.commands")))))
(popup-exists? "editor.commands"))
(defn open-selected-block!
[direction e]

View File

@@ -20,6 +20,7 @@
[frontend.components.property.dialog :as property-dialog]
[frontend.components.repo :as repo]
[frontend.components.select :as select]
[frontend.components.selection :as selection]
[frontend.components.settings :as settings]
[frontend.components.shell :as shell]
[frontend.components.user.login :as login]
@@ -927,11 +928,15 @@
(when-let [blocks (and block (db-model/get-block-immediate-children (state/get-current-repo) (:block/uuid block)))]
(editor-handler/toggle-blocks-as-own-order-list! blocks)))
(defn- editor-new-property [block target opts]
(defn- editor-new-property [block target {:keys [selected-blocks] :as opts}]
(let [editing-block (state/get-edit-block)
pos (state/get-edit-pos)
edit-block-or-selected (if editing-block
edit-block-or-selected (cond
editing-block
[editing-block]
(seq selected-blocks)
selected-blocks
:else
(seq (keep #(db/entity [:block/uuid %]) (state/get-selection-block-ids))))
current-block (when-let [s (state/get-current-page)]
(when (util/uuid-string? s)
@@ -1069,6 +1074,24 @@
(defmethod handle :editor/run-query-command [_]
(editor-handler/run-query-command!))
(defmethod handle :editor/show-action-bar []
(let [selection (state/get-selection-blocks)
first-visible-block (some #(when (util/el-visible-in-viewport? % true) %) selection)]
(when first-visible-block
(shui/popup-hide! :selection-action-bar)
(shui/popup-show!
first-visible-block
(fn []
(selection/action-bar))
{:id :selection-action-bar
:content-props {:side "top"
:class "!py-0 !px-0 !border-none"}
:auto-side? false
:align :start}))))
(defmethod handle :editor/hide-action-bar []
(shui/popup-hide! :selection-action-bar))
(defn run!
[]
(let [chan (state/get-events-chan)]

View File

@@ -5,6 +5,7 @@
(:refer-clojure :exclude [map filter mapcat concat remove])
(:require [cljs.core.match :refer [match]]
[clojure.string :as string]
[frontend.common.file.core :as common-file]
[frontend.db :as db]
[frontend.format.mldoc :as mldoc]
[frontend.modules.file.core :as outliner-file]
@@ -12,11 +13,10 @@
[frontend.persist-db.browser :as db-browser]
[frontend.state :as state]
[frontend.util :as util :refer [concatv mapcatv removev]]
[frontend.common.file.core :as common-file]
[logseq.db :as ldb]
[malli.core :as m]
[malli.util :as mu]
[promesa.core :as p]
[logseq.db :as ldb]))
[promesa.core :as p]))
;;; TODO: split frontend.handler.export.text related states
(def ^:dynamic *state*
@@ -64,8 +64,14 @@
(outliner-file/tree->file-content {:init-level init-level})))
(defn root-block-uuids->content
[repo root-block-uuids]
(let [contents (mapv #(get-blocks-contents repo %) root-block-uuids)]
[repo root-block-uuids & {:keys [page-title-only?]}]
(let [contents (mapv (fn [id]
(if-let [page (and page-title-only?
(let [e (db/entity [:block/uuid id])]
(when (:block/name e)
e)))]
(:block/title page)
(get-blocks-contents repo id))) root-block-uuids)]
(string/join "\n" (mapv string/trim-newline contents))))
(declare remove-block-ast-pos Properties-block-ast?)

View File

@@ -506,24 +506,25 @@
"options:
:indent-style \"dashes\" | \"spaces\" | \"no-indent\"
:remove-options [:emphasis :page-ref :tag :property]
:other-options {:keep-only-level<=N int :newline-after-block bool}"
:other-options {:keep-only-level<=N int :newline-after-block bool}
:page-title-only? boolean"
[repo root-block-uuids-or-page-uuid options]
{:pre [(or (coll? root-block-uuids-or-page-uuid)
(uuid? root-block-uuids-or-page-uuid))]}
(util/profile
:export-blocks-as-markdown
(try
(let [content
(if (uuid? root-block-uuids-or-page-uuid)
:export-blocks-as-markdown
(try
(let [content
(if (uuid? root-block-uuids-or-page-uuid)
;; page
(common/get-page-content root-block-uuids-or-page-uuid)
(common/root-block-uuids->content repo root-block-uuids-or-page-uuid))
first-block (and (coll? root-block-uuids-or-page-uuid)
(db/entity [:block/uuid (first root-block-uuids-or-page-uuid)]))
format (get first-block :block/format :markdown)]
(export-helper content format options))
(catch :default e
(js/console.error e)))))
(common/get-page-content root-block-uuids-or-page-uuid)
(common/root-block-uuids->content repo root-block-uuids-or-page-uuid options))
first-block (and (coll? root-block-uuids-or-page-uuid)
(db/entity [:block/uuid (first root-block-uuids-or-page-uuid)]))
format (get first-block :block/format :markdown)]
(export-helper content format options))
(catch :default e
(js/console.error e)))))
(defn export-files-as-markdown
"options see also `export-blocks-as-markdown`"
@@ -531,7 +532,7 @@
(mapv
(fn [{:keys [path title content]}]
(util/profile (print-str :export-files-as-markdown title)
[(or path title) (export-helper content :markdown options)]))
[(or path title) (export-helper content :markdown options)]))
files))
(defn export-repo-as-markdown!

View File

@@ -177,10 +177,23 @@
(state/toggle-wide-mode!))
;; auto-complete
(defn- reorder-matched
"Reorder matched if grouped"
[state]
(let [[matched {:keys [grouped?]}] (:rum/args state)]
(if grouped?
(let [*idx (atom -1)
inc-idx #(swap! *idx inc)]
(->>
(for [[_group matched] (group-by :group matched)]
(doall (map (fn [item] (inc-idx) item) matched)))
(apply concat)))
matched)))
(defn auto-complete-prev
[state e]
(let [current-idx (get state :frontend.ui/current-idx)
matched (first (:rum/args state))]
matched (reorder-matched state)]
(util/stop e)
(cond
(>= @current-idx 1)
@@ -197,7 +210,7 @@
(defn auto-complete-next
[state e]
(let [current-idx (get state :frontend.ui/current-idx)
matched (first (:rum/args state))]
matched (reorder-matched state)]
(util/stop e)
(let [total (count matched)]
(if (>= @current-idx (dec total))
@@ -211,7 +224,8 @@
(defn auto-complete-complete
[state e]
(let [[matched {:keys [on-chosen on-enter]}] (:rum/args state)
(let [[_matched {:keys [on-chosen on-enter]}] (:rum/args state)
matched (reorder-matched state)
current-idx (get state :frontend.ui/current-idx)]
(util/stop e)
(if (and (seq matched)
@@ -222,7 +236,8 @@
(defn auto-complete-shift-complete
[state e]
(let [[matched {:keys [on-chosen on-shift-chosen on-enter]}] (:rum/args state)
(let [[_matched {:keys [on-chosen on-shift-chosen on-enter]}] (:rum/args state)
matched (reorder-matched state)
current-idx (get state :frontend.ui/current-idx)]
(util/stop e)
(if (and (seq matched)

View File

@@ -260,6 +260,7 @@
:command-palette/commands (atom [])
:view/components {}
:view/selected-blocks nil
:srs/mode? false
@@ -1187,8 +1188,21 @@ Similar to re-frame subscriptions"
(doseq [node nodes]
(dom/add-class! node "selected")))
(defn get-events-chan
[]
(:system/events @state))
(defn pub-event!
{:malli/schema [:=> [:cat vector?] :any]}
[payload]
(let [d (p/deferred)
chan (get-events-chan)]
(async/put! chan [payload d])
d))
(defn- set-selection-blocks-aux!
[blocks]
(set-state! :view/selected-blocks nil)
(let [selected-ids (set (get-selected-block-ids @(:selection/blocks @state)))
_ (set-state! :selection/blocks blocks)
new-ids (set (get-selection-block-ids))
@@ -1212,7 +1226,8 @@ Similar to re-frame subscriptions"
(set-state! :selection/blocks nil)
(set-state! :selection/direction nil)
(set-state! :selection/start-block nil)
(set-state! :selection/selected-all? false))
(set-state! :selection/selected-all? false)
(pub-event! [:editor/hide-action-bar]))
(defn clear-selection!
[]
@@ -1368,7 +1383,8 @@ Similar to re-frame subscriptions"
(set-state! :editor/content {})
(set-state! :ui/select-query-cache {})
(set-state! :editor/block-refs #{})
(set-state! :editor/action-data nil))
(set-state! :editor/action-data nil)
(set-state! :view/selected-blocks nil))
(defn into-code-editor-mode!
[]
@@ -1831,18 +1847,6 @@ Similar to re-frame subscriptions"
([] (open-settings! true))
([active-tab] (set-state! :ui/settings-open? active-tab)))
(defn get-events-chan
[]
(:system/events @state))
(defn pub-event!
{:malli/schema [:=> [:cat vector?] :any]}
[payload]
(let [d (p/deferred)
chan (get-events-chan)]
(async/put! chan [payload d])
d))
(defn sidebar-add-block!
[repo db-id block-type]
(when (not (util/sm-breakpoint?))

View File

@@ -517,20 +517,20 @@
(let [*current-idx (get state ::current-idx)
*groups (atom #{})
render-f (fn [matched]
(for [[idx item] (medley/indexed matched)]
(for [[idx item] matched]
(let [react-key (str idx)
item-cp
[:div.menu-link-wrap
{:key react-key
;; mouse-move event to indicate that cursor moved by user
;; mouse-move event to indicate that cursor moved by user
:on-mouse-move #(reset! *current-idx idx)}
(let [chosen? (= @*current-idx idx)]
(menu-link
{:id (str "ac-" react-key)
:tab-index "0"
:class (when chosen? "chosen")
;; TODO: should have more tests on touch devices
;:on-pointer-down #(util/stop %)
;; TODO: should have more tests on touch devices
;:on-pointer-down #(util/stop %)
:on-click (fn [e]
(util/stop e)
(when-not (:disabled? item)
@@ -552,13 +552,16 @@
[:div#ui__ac-inner.hide-scrollbar
(when header header)
(if grouped?
(for [[group matched] (group-by :group matched)]
(if group
[:div
[:div.ui__ac-group-name group]
(render-f matched)]
(render-f matched)))
(render-f matched))]
(let [*idx (atom -1)
inc-idx #(swap! *idx inc)]
(for [[group matched] (group-by :group matched)]
(let [matched' (doall (map (fn [item] [(inc-idx) item]) matched))]
(if group
[:div
[:div.ui__ac-group-name group]
(render-f matched')]
(render-f matched')))))
(render-f (medley/indexed matched)))]
(when empty-placeholder
empty-placeholder))]))
@@ -1016,13 +1019,11 @@
:small? true)]]))
(rum/defc tooltip
[trigger tooltip-content]
[trigger tooltip-content & {:keys [trigger-props]}]
(shui/tooltip-provider
(shui/tooltip
(shui/tooltip-trigger
trigger)
(shui/tooltip-content
tooltip-content))))
(shui/tooltip-trigger trigger-props trigger)
(shui/tooltip-content tooltip-content))))
(rum/defc DelDateButton
[on-delete]

View File

@@ -16,6 +16,7 @@
[frontend.worker.db.migrate :as db-migrate]
[frontend.worker.db.validate :as worker-db-validate]
[frontend.worker.device :as worker-device]
[frontend.worker.embedding]
[frontend.worker.export :as worker-export]
[frontend.worker.file :as file]
[frontend.worker.handler.page :as worker-page]
@@ -42,8 +43,7 @@
[logseq.outliner.op :as outliner-op]
[me.tonsky.persistent-sorted-set :as set :refer [BTSet]]
[promesa.core :as p]
[shadow.cljs.modern :refer [defclass]]
[frontend.worker.embedding]))
[shadow.cljs.modern :refer [defclass]]))
(defonce *sqlite worker-state/*sqlite)
(defonce *sqlite-conns worker-state/*sqlite-conns)
@@ -336,6 +336,8 @@
(= "db" (:kv/value (d/entity @conn :logseq.kv/db-type)))))]
(swap! *datascript-conns assoc repo conn)
(swap! *client-ops-conns assoc repo client-ops-conn)
(when (not= client-op/schema-in-db (d/schema @client-ops-conn))
(d/reset-schema! client-ops-conn client-op/schema-in-db))
(when (and db-based? (not initial-data-exists?) (not datoms))
(let [config (or config "")
initial-data (sqlite-create-graph/build-db-initial-data config
@@ -438,7 +440,6 @@
(let [result (when-not (= result @worker-state/*state) result)]
(ldb/write-transit-str result)))))
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defclass DBWorker
(extends js/Object)
@@ -594,7 +595,7 @@
;; (prn :debug :transact :tx-data tx-data' :tx-meta tx-meta')
(worker-util/profile "Worker db transact"
(ldb/transact! conn tx-data' tx-meta')))
(ldb/transact! conn tx-data' tx-meta')))
nil)
(catch :default e
(prn :debug :error)
@@ -705,11 +706,11 @@
(when-let [conn (worker-state/get-datascript-conn repo)]
(try
(worker-util/profile
"apply outliner ops"
(let [ops (ldb/read-transit-str ops-str)
opts (ldb/read-transit-str opts-str)
result (outliner-op/apply-ops! repo conn ops (worker-state/get-date-formatter repo) opts)]
(ldb/write-transit-str result)))
"apply outliner ops"
(let [ops (ldb/read-transit-str ops-str)
opts (ldb/read-transit-str opts-str)
result (outliner-op/apply-ops! repo conn ops (worker-state/get-date-formatter repo) opts)]
(ldb/write-transit-str result)))
(catch :default e
(let [data (ex-data e)
{:keys [type payload]} (when (map? data) data)]
@@ -973,7 +974,7 @@
(glogi-console/install!)
(check-worker-scope!)
(let [^js obj #_{:clj-kondo/ignore [:unresolved-symbol]}
(DBWorker.)]
(DBWorker.)]
(outliner-register-op-handlers!)
(worker-state/set-worker-object! obj)
(<ratelimit-file-writes!)

View File

@@ -12,6 +12,7 @@
[frontend.worker.rtc.skeleton :as r.skeleton]
[frontend.worker.rtc.ws :as ws]
[frontend.worker.rtc.ws-util :as ws-util]
[logseq.db :as ldb]
[logseq.db.frontend.schema :as db-schema]
[missionary.core :as m]))
@@ -36,7 +37,8 @@
"Return a task: get or create a mws(missionary wrapped websocket).
see also `ws/get-mws-create`.
But ensure `register-graph-updates` and `calibrate-graph-skeleton` has been sent"
[get-ws-create-task graph-uuid major-schema-version repo conn *last-calibrate-t *online-users add-log-fn]
[get-ws-create-task graph-uuid major-schema-version repo conn
*last-calibrate-t *online-users *server-schema-version add-log-fn]
(assert (some? graph-uuid))
(let [*sent (atom {}) ;; ws->bool
]
@@ -75,8 +77,10 @@
(let [t (client-op/get-local-tx repo)]
(when (or (nil? @*last-calibrate-t)
(< 500 (- t @*last-calibrate-t)))
;; (m/? (r.skeleton/new-task--calibrate-graph-skeleton get-ws-create-task graph-uuid conn t))
(m/? (r.skeleton/new-task--calibrate-graph-skeleton get-ws-create-task graph-uuid major-schema-version @conn))
(let [{:keys [server-schema-version _server-builtin-db-idents]}
(m/? (r.skeleton/new-task--calibrate-graph-skeleton
get-ws-create-task graph-uuid major-schema-version @conn))]
(reset! *server-schema-version server-schema-version))
(reset! *last-calibrate-t t)))
(swap! *sent assoc ws true))
ws))))
@@ -142,16 +146,14 @@
(defn- schema-av-coll->update-schema-op
[db block-uuid db-ident schema-av-coll]
(when (and (seq schema-av-coll) db-ident)
(let [db-ident-ns (namespace db-ident)]
(when (and (string/ends-with? db-ident-ns ".property")
(not= db-ident-ns "logseq.property"))
(when-let [ent (d/entity db db-ident)]
[:update-schema
(cond-> {:block-uuid block-uuid
:db/ident db-ident
:db/valueType (or (:db/valueType ent) :db.type/string)}
(:db/cardinality ent) (assoc :db/cardinality (:db/cardinality ent))
(:db/index ent) (assoc :db/index (:db/index ent)))])))))
(when-let [ent (d/entity db db-ident)]
(when (ldb/property? ent)
[:update-schema
(cond-> {:block-uuid block-uuid
:db/ident db-ident
:db/valueType (or (:db/valueType ent) :db.type/string)}
(:db/cardinality ent) (assoc :db/cardinality (:db/cardinality ent))
(:db/index ent) (assoc :db/index (:db/index ent)))]))))
(defn- av-coll->card-one-attrs
[db-schema av-coll]
@@ -268,6 +270,30 @@
(:remote-ops (local-block-ops->remote-ops db block-ops-map))]))
block-ops-map-coll))
(defmulti ^:private local-db-ident-kv-ops->remote-ops-aux (fn [op-type & _] op-type))
(defmethod local-db-ident-kv-ops->remote-ops-aux :update-kv-value
[_ op]
(let [op-value (last op)
db-ident (:db-ident op-value)
value (:value op-value)]
[:update-kv-value {:db-ident db-ident :value (ldb/write-transit-str value)}]))
(defmethod local-db-ident-kv-ops->remote-ops-aux :db-ident
[_ _op]
;; ignore
)
(defn- local-db-ident-kv-ops->remote-ops
[db-ident-kv-ops-map]
(keep
(fn [[op-type op]]
(local-db-ident-kv-ops->remote-ops-aux op-type op))
db-ident-kv-ops-map))
(defn- gen-db-ident-kv-remote-ops
[db-ident-kv-ops-map-coll]
(mapcat local-db-ident-kv-ops->remote-ops db-ident-kv-ops-map-coll))
(defn- merge-remove-remove-ops
[remote-remove-ops]
(when-let [block-uuids (->> remote-remove-ops
@@ -326,61 +352,73 @@
(concat update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops)))
(defn- rollback
[repo block-ops-map-coll]
(let [ops (mapcat
(fn [m]
(keep (fn [[k op]]
(when (not= :block/uuid k)
op))
m))
block-ops-map-coll)]
(client-op/add-ops repo ops)
[repo block-ops-map-coll db-ident-kv-ops-map-coll]
(let [block-ops
(mapcat
(fn [m]
(keep (fn [[k op]]
(when-not (keyword-identical? :block/uuid k)
op))
m))
block-ops-map-coll)
db-ident-kv-ops
(mapcat
(fn [m]
(keep (fn [[k op]]
(when-not (keyword-identical? :db-ident k)
op))
m))
db-ident-kv-ops-map-coll)]
(client-op/add-ops! repo block-ops)
(client-op/add-ops! repo db-ident-kv-ops)
nil))
(defn new-task--push-local-ops
"Return a task: push local updates"
[repo conn graph-uuid major-schema-version date-formatter get-ws-create-task *remote-profile? add-log-fn]
(m/sp
(let [block-ops-map-coll (client-op/get&remove-all-block-ops repo)]
(when-let [block-uuid->remote-ops (not-empty (gen-block-uuid->remote-ops @conn block-ops-map-coll))]
(when-let [ops-for-remote (rtc-schema/to-ws-ops-decoder
(sort-remote-ops
block-uuid->remote-ops))]
(let [local-tx (client-op/get-local-tx repo)
r (try
(m/? (ws-util/send&recv get-ws-create-task
(cond-> {:action "apply-ops"
:graph-uuid graph-uuid :schema-version (str major-schema-version)
:ops ops-for-remote :t-before (or local-tx 1)}
(true? @*remote-profile?) (assoc :profile true))))
(catch :default e
(rollback repo block-ops-map-coll)
(throw e)))]
(if-let [remote-ex (:ex-data r)]
(do (add-log-fn :rtc.log/push-local-update remote-ex)
(case (:type remote-ex)
;; - :graph-lock-failed
;; conflict-update remote-graph, keep these local-pending-ops
;; and try to send ops later
:graph-lock-failed
(rollback repo block-ops-map-coll)
;; - :graph-lock-missing
;; this case means something wrong in remote-graph data,
;; nothing to do at client-side
:graph-lock-missing
(do (rollback repo block-ops-map-coll)
(throw r.ex/ex-remote-graph-lock-missing))
(let [block-ops-map-coll (client-op/get&remove-all-block-ops repo)
db-ident-kv-ops-map-coll (client-op/get&remove-all-db-ident-kv-ops repo)
block-uuid->remote-ops (not-empty (gen-block-uuid->remote-ops @conn block-ops-map-coll))
other-remote-ops (gen-db-ident-kv-remote-ops db-ident-kv-ops-map-coll)
remote-ops (concat (when block-uuid->remote-ops (sort-remote-ops block-uuid->remote-ops))
other-remote-ops)]
(when-let [ops-for-remote (rtc-schema/to-ws-ops-decoder remote-ops)]
(let [local-tx (client-op/get-local-tx repo)
r (try
(m/? (ws-util/send&recv get-ws-create-task
(cond-> {:action "apply-ops"
:graph-uuid graph-uuid :schema-version (str major-schema-version)
:ops ops-for-remote :t-before (or local-tx 1)}
(true? @*remote-profile?) (assoc :profile true))))
(catch :default e
(rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
(throw e)))]
(if-let [remote-ex (:ex-data r)]
(do (add-log-fn :rtc.log/push-local-update remote-ex)
(case (:type remote-ex)
;; - :graph-lock-failed
;; conflict-update remote-graph, keep these local-pending-ops
;; and try to send ops later
:graph-lock-failed
(rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
;; - :graph-lock-missing
;; this case means something wrong in remote-graph data,
;; nothing to do at client-side
:graph-lock-missing
(do (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
(throw r.ex/ex-remote-graph-lock-missing))
:rtc.exception/get-s3-object-failed
(rollback repo block-ops-map-coll)
;; else
(do (rollback repo block-ops-map-coll)
(throw (ex-info "Unavailable1" {:remote-ex remote-ex})))))
:rtc.exception/get-s3-object-failed
(rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
;; else
(do (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
(throw (ex-info "Unavailable1" {:remote-ex remote-ex})))))
(do (assert (pos? (:t r)) r)
(r.remote-update/apply-remote-update
graph-uuid repo conn date-formatter {:type :remote-update :value r} add-log-fn)
(add-log-fn :rtc.log/push-local-update {:remote-t (:t r)})))))))))
(do (assert (pos? (:t r)) r)
(r.remote-update/apply-remote-update
graph-uuid repo conn date-formatter {:type :remote-update :value r} add-log-fn)
(add-log-fn :rtc.log/push-local-update {:remote-t (:t r)}))))))))
(defn new-task--pull-remote-data
[repo conn graph-uuid major-schema-version date-formatter get-ws-create-task add-log-fn]

View File

@@ -12,6 +12,14 @@
(def op-schema
[:multi {:dispatch first}
[:update-kv-value
;; update :logseq.kv/xxx entities
[:catn
[:op :keyword]
[:t :int]
[:value [:map
[:db-ident :keyword]
[:value :any]]]]]
[:move
[:catn
[:op :keyword]
@@ -60,15 +68,17 @@
(def ops-schema [:sequential op-schema])
(def ops-coercer (ma/coercer ops-schema mt/json-transformer nil
#(do (log/error ::bad-ops (:value %))
(ma/-fail! ::ops-schema %))))
(ma/-fail! ::ops-schema (select-keys % [:value])))))
(def ^:private block-op-types #{:move :remove :update-page :remove-page :update})
(def ^:private asset-op-types #{:update-asset :remove-asset})
(def ^:private db-ident-kv-op-types #{:update-kv-value})
(def schema-in-db
"TODO: rename this db-name from client-op to client-metadata+op.
and move it to its own namespace."
{:block/uuid {:db/unique :db.unique/identity}
:db-ident {:db/unique :db.unique/identity}
:local-tx {:db/index true}
:graph-uuid {:db/index true}
:aes-key-jwk {:db/index true}
@@ -190,13 +200,66 @@
(cons [:db/add tmpid :block/uuid block-uuid] tx-data))))
block-uuid->op-type->op)))
(defn add-ops
(defn- generate-ident-kv-ops-tx-data
[client-ops-db ops]
(let [sorted-ops (sort-by second ops)
db-idents (map (fn [[_op-type _t value]] (:db-ident value)) sorted-ops)
ents (d/pull-many client-ops-db '[*] (map (fn [db-ident] [:db-ident db-ident]) db-idents))
op-types [:update-kv-value]
init-db-ident->op-type->op
(into {}
(map (fn [ent]
[(:db-ident ent)
(into {}
(keep
(fn [op-type]
(when-let [op (get ent op-type)]
[op-type op])))
op-types)]))
ents)
db-ident->op-type->op
(reduce
(fn [r op]
(let [[op-type _t value] op
db-ident (:db-ident value)]
(case op-type
:update-kv-value
(assoc-in r [db-ident :update-kv-value] op))))
init-db-ident->op-type->op sorted-ops)]
(mapcat
(fn [[db-ident op-type->op]]
(let [tmpid (str db-ident)]
(when-let [tx-data (not-empty
(keep
(fn [[op-type op]]
(when op [:db/add tmpid op-type op]))
op-type->op))]
(cons [:db/add tmpid :db-ident db-ident] tx-data))))
db-ident->op-type->op)))
(defn- partition-ops
"Return [db-ident-kv-ops block-ops]"
[ops]
((juxt :db-ident :block-uuid)
(group-by
(fn [[_op-type _t value :as op]]
(cond
(:block-uuid value) :block-uuid
(:db-ident value) :db-ident
:else (throw (ex-info "invalid op" {:op op}))))
ops)))
(defn add-ops!
[repo ops]
(let [conn (worker-state/get-client-ops-conn repo)
ops (ops-coercer ops)]
(assert (some? conn) repo)
(when-let [tx-data (not-empty (generate-block-ops-tx-data @conn ops))]
(d/transact! conn tx-data))))
(when (seq ops)
(let [conn (worker-state/get-client-ops-conn repo)
ops (ops-coercer ops)
_ (assert (some? conn) repo)
[db-ident-kv-ops block-ops] (partition-ops ops)
tx-data1 (when (seq block-ops) (generate-block-ops-tx-data @conn block-ops))
tx-data2 (when (seq db-ident-kv-ops) (generate-ident-kv-ops-tx-data @conn db-ident-kv-ops))]
(when-let [tx-data (not-empty (concat tx-data1 tx-data2))]
(d/transact! conn tx-data)))))
(defn- get-all-block-ops*
"Return e->op-map"
@@ -207,7 +270,8 @@
(let [op-map (into {}
(keep (fn [datom]
(let [a (:a datom)]
(when (or (= :block/uuid a) (contains? block-op-types a))
(when (or (keyword-identical? :block/uuid a)
(contains? block-op-types a))
[a (:v datom)]))))
datoms)]
(when (and (:block/uuid op-map)
@@ -216,7 +280,26 @@
[e op-map]))))
(into {})))
(defn get&remove-all-block-ops*
(defn- get-all-db-ident-kv-ops*
"Return e->op-map"
[db]
(let [db-ident-datoms (d/datoms db :avet :db-ident)
es (map :e db-ident-datoms)]
(->> (map (fn [e] [e (d/datoms db :eavt e)]) es)
(keep (fn [[e datoms]]
(let [op-map (into {}
(keep (fn [datom]
(let [a (:a datom)]
(when (or (keyword-identical? :db-ident a)
(contains? db-ident-kv-op-types a))
[a (:v datom)]))))
datoms)]
(when (and (:db-ident op-map)
(> (count op-map) 1))
[e op-map]))))
(into {}))))
(defn- get&remove-all-block-ops*
[conn]
(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))
@@ -224,6 +307,14 @@
(d/transact! conn retract-all-tx-data)
(vals e->op-map)))
(defn- get&remove-all-db-ident-kv-ops*
[conn]
(let [e->op-map (get-all-db-ident-kv-ops* @conn)
retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) db-ident-kv-op-types))
(keys e->op-map))]
(d/transact! conn retract-all-tx-data)
(vals e->op-map)))
(defn get-all-block-ops
[repo]
(when-let [conn (worker-state/get-client-ops-conn repo)]
@@ -234,6 +325,12 @@
m))
(vals (get-all-block-ops* @conn)))))
(comment
(defn get-all-db-ident-kv-ops
[repo]
(when-let [conn (worker-state/get-client-ops-conn repo)]
(get-all-db-ident-kv-ops* @conn))))
(defn get&remove-all-block-ops
"Return coll of
{:block/uuid ...
@@ -244,6 +341,11 @@
(when-let [conn (worker-state/get-client-ops-conn repo)]
(get&remove-all-block-ops* conn)))
(defn get&remove-all-db-ident-kv-ops
[repo]
(when-let [conn (worker-state/get-client-ops-conn repo)]
(get&remove-all-db-ident-kv-ops* conn)))
(defn get-unpushed-block-ops-count
[repo]
(when-let [conn (worker-state/get-client-ops-conn repo)]
@@ -327,7 +429,7 @@
(let [op-map (into {}
(keep (fn [datom]
(let [a (:a datom)]
(when (or (= :block/uuid a) (contains? asset-op-types a))
(when (or (keyword-identical? :block/uuid a) (contains? asset-op-types a))
[a (:v datom)]))))
datoms)]
(when (and (:block/uuid op-map)

View File

@@ -12,6 +12,7 @@
[frontend.worker.rtc.exception :as r.ex]
[frontend.worker.rtc.full-upload-download-graph :as r.upload-download]
[frontend.worker.rtc.log-and-state :as rtc-log-and-state]
[frontend.worker.rtc.migrate :as r.migrate]
[frontend.worker.rtc.remote-update :as r.remote-update]
[frontend.worker.rtc.skeleton]
[frontend.worker.rtc.ws :as ws]
@@ -157,6 +158,24 @@
ws-state (assoc :ws-state ws-state)))
(m/reductions {} nil ws-state-flow)))
(defn- add-migration-client-ops!
[repo db server-schema-version]
(when server-schema-version
(let [client-schema-version (ldb/get-graph-schema-version db)
added-ops (r.migrate/add-migration-client-ops! repo db server-schema-version client-schema-version)]
(when (seq added-ops)
(log/info :add-migration-client-ops
{:repo repo
:server-schema-version server-schema-version
:client-schema-version client-schema-version})))))
(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})))
(defonce ^:private *rtc-lock (atom nil))
(defn- holding-rtc-lock
"Use this fn to prevent multiple rtc-loops at same time.
@@ -188,15 +207,16 @@
*last-calibrate-t (atom nil)
*online-users (atom nil)
*assets-sync-loop-canceler (atom nil)
*server-schema-version (atom nil)
started-dfv (m/dfv)
add-log-fn (fn [type message]
(assert (map? message) message)
(rtc-log-and-state/rtc-log type (assoc message :graph-uuid graph-uuid)))
{:keys [*current-ws get-ws-create-task]}
(gen-get-ws-create-map--memoized ws-url)
get-ws-create-task (r.client/ensure-register-graph-updates
get-ws-create-task graph-uuid major-schema-version
repo conn *last-calibrate-t *online-users add-log-fn)
get-ws-create-task (r.client/ensure-register-graph-updates
get-ws-create-task graph-uuid major-schema-version
repo conn *last-calibrate-t *online-users *server-schema-version add-log-fn)
{:keys [assets-sync-loop-task]}
(r.asset/create-assets-sync-loop repo get-ws-create-task graph-uuid major-schema-version conn *auto-push?)
mixed-flow (create-mixed-flow repo get-ws-create-task *auto-push? *online-users)]
@@ -214,6 +234,8 @@
;; init run to open a ws
(m/? get-ws-create-task)
(started-dfv true)
(update-remote-schema-version! conn @*server-schema-version)
(add-migration-client-ops! repo @conn @*server-schema-version)
(reset! *assets-sync-loop-canceler
(c.m/run-task assets-sync-loop-task :assets-sync-loop-task))
(->>

View File

@@ -1,148 +1,14 @@
(ns frontend.worker.rtc.db-listener
"listen datascript changes, infer operations from the db tx-report"
(:require [clojure.string :as string]
[datascript.core :as d]
[frontend.worker.db-listener :as db-listener]
(:require [frontend.worker.db-listener :as db-listener]
[frontend.worker.rtc.client-op :as client-op]
[frontend.worker.rtc.const :as rtc-const]
[logseq.db :as ldb]
[logseq.db.frontend.property :as db-property]))
(defn- latest-add?->v->t
[add?->v->t]
(let [latest-add (first (sort-by second > (seq (add?->v->t true))))
latest-retract (first (sort-by second > (seq (add?->v->t false))))]
(cond
(nil? latest-add) {false (conj {} latest-retract)}
(nil? latest-retract) {true (conj {} latest-add)}
(= (second latest-add) (second latest-retract)) {true (conj {} latest-add)
false (conj {} latest-retract)}
(> (second latest-add) (second latest-retract)) {true (conj {} latest-add)}
:else {false (conj {} latest-retract)})))
(def ^:private watched-attrs
#{:block/title :block/created-at :block/updated-at :block/alias
:block/tags :block/link :block/journal-day
:logseq.property/classes :logseq.property/value
:db/index :db/valueType :db/cardinality})
(def ^:private watched-attr-ns
(conj db-property/logseq-property-namespaces "logseq.class"))
(defn- watched-attr?
[attr]
(or (contains? watched-attrs attr)
(let [ns (namespace attr)]
(or (contains? watched-attr-ns ns)
(string/ends-with? ns ".property")
(string/ends-with? ns ".class")))))
(defn- ref-attr?
[db attr]
(= :db.type/ref (get-in (d/schema db) [attr :db/valueType])))
(defn- update-op-av-coll
[db-before db-after a->add?->v->t]
(mapcat
(fn [[a add?->v->t]]
(mapcat
(fn [[add? v->t]]
(keep
(fn [[v t]]
(let [ref? (ref-attr? db-after a)]
(case [add? ref?]
[true true]
(when-let [v-uuid (:block/uuid (d/entity db-after v))]
[a v-uuid t add?])
[false true]
(when-let [v-uuid (:block/uuid
(or (d/entity db-after v)
(d/entity db-before v)))]
[a v-uuid t add?])
([true false] [false false]) [a (ldb/write-transit-str v) t add?])))
v->t))
add?->v->t))
a->add?->v->t))
(defn- redundant-update-op-av-coll?
[av-coll]
(every? (fn [av] (keyword-identical? :block/updated-at (first av))) av-coll))
(defn- max-t
[a->add?->v->t]
(apply max (mapcat vals (mapcat vals (vals a->add?->v->t)))))
(defn- get-first-vt
[add?->v->t k]
(some-> add?->v->t (get k) first))
(defn- entity-datoms=>ops
[db-before db-after e->a->add?->v->t ignore-attr-set entity-datoms]
(let [e (ffirst entity-datoms)
entity (d/entity db-after e)
{block-uuid :block/uuid} entity
a->add?->v->t (e->a->add?->v->t e)
{add?->block-name->t :block/name
add?->block-title->t :block/title
add?->block-uuid->t :block/uuid
add?->block-parent->t :block/parent
add?->block-order->t :block/order}
a->add?->v->t
[retract-block-uuid t1] (some-> add?->block-uuid->t (get false) first)
[retract-block-name _] (some-> add?->block-name->t (get false) first)
[add-block-name t2] (some-> add?->block-name->t latest-add?->v->t (get-first-vt true))
[add-block-title t3] (some-> add?->block-title->t latest-add?->v->t (get-first-vt true))
[add-block-parent t4] (some-> add?->block-parent->t latest-add?->v->t (get-first-vt true))
[add-block-order t5] (some-> add?->block-order->t latest-add?->v->t (get-first-vt true))
a->add?->v->t* (into {}
(filter
(fn [[a _]]
(and (watched-attr? a)
(not (contains? ignore-attr-set a)))))
a->add?->v->t)]
(cond
(and retract-block-uuid retract-block-name)
[[:remove-page t1 {:block-uuid retract-block-uuid}]]
retract-block-uuid
[[:remove t1 {:block-uuid retract-block-uuid}]]
:else
(let [ops (cond-> []
(or add-block-parent add-block-order)
(conj [:move (or t4 t5) {:block-uuid block-uuid}])
(or add-block-name
(and (ldb/page? entity) add-block-title))
(conj [:update-page (or t2 t3) {:block-uuid block-uuid}]))
update-op (when-let [av-coll (not-empty (update-op-av-coll db-before db-after a->add?->v->t*))]
(when-not (redundant-update-op-av-coll? av-coll)
(let [t (max-t a->add?->v->t*)]
[:update t {:block-uuid block-uuid :av-coll av-coll}])))]
(cond-> ops update-op (conj update-op))))))
(defn- generate-rtc-ops
[repo db-before db-after same-entity-datoms-coll e->a->v->add?->t]
(let [ops (mapcat
(partial entity-datoms=>ops
db-before db-after e->a->v->add?->t rtc-const/ignore-attrs-when-syncing)
same-entity-datoms-coll)]
(when (seq ops)
(client-op/add-ops repo ops))))
[frontend.worker.rtc.gen-client-op :as gen-client-op]))
(comment
;; TODO: make it a qualified-keyword
(defkeywords
:persist-op? {:doc "tx-meta option, generate rtc ops when not nil (default true)"}))
(defn- entity-datoms=>a->add?->v->t
[entity-datoms]
(reduce
(fn [m datom]
(let [[_e a v t add?] datom]
(assoc-in m [a add? v] t)))
{} entity-datoms))
(defmethod db-listener/listen-db-changes :gen-rtc-ops
[_
{:keys [repo same-entity-datoms-coll id->same-entity-datoms]}
@@ -151,5 +17,7 @@
(:persist-op? tx-meta true))
(let [e->a->add?->v->t (update-vals
id->same-entity-datoms
entity-datoms=>a->add?->v->t)]
(generate-rtc-ops repo db-before db-after same-entity-datoms-coll e->a->add?->v->t))))
gen-client-op/entity-datoms=>a->add?->v->t)
ops (gen-client-op/generate-rtc-ops db-before db-after same-entity-datoms-coll e->a->add?->v->t)]
(when (seq ops)
(client-op/add-ops! repo ops)))))

View File

@@ -0,0 +1,160 @@
(ns frontend.worker.rtc.gen-client-op
"Generate client-ops from entities/datoms"
(:require [clojure.string :as string]
[datascript.core :as d]
[frontend.worker.rtc.const :as rtc-const]
[logseq.db :as ldb]
[logseq.db.frontend.property :as db-property]))
(defn- latest-add?->v->t
[add?->v->t]
(let [latest-add (first (sort-by second > (seq (add?->v->t true))))
latest-retract (first (sort-by second > (seq (add?->v->t false))))]
(cond
(nil? latest-add) {false (conj {} latest-retract)}
(nil? latest-retract) {true (conj {} latest-add)}
(= (second latest-add) (second latest-retract)) {true (conj {} latest-add)
false (conj {} latest-retract)}
(> (second latest-add) (second latest-retract)) {true (conj {} latest-add)}
:else {false (conj {} latest-retract)})))
(def ^:private watched-attrs
#{:block/title :block/created-at :block/updated-at :block/alias
:block/tags :block/link :block/journal-day
:logseq.property/classes :logseq.property/value
:db/index :db/valueType :db/cardinality})
(def ^:private watched-attr-ns
(conj db-property/logseq-property-namespaces "logseq.class"))
(defn- watched-attr?
[attr]
(or (contains? watched-attrs attr)
(let [ns (namespace attr)]
(or (contains? watched-attr-ns ns)
(string/ends-with? ns ".property")
(string/ends-with? ns ".class")))))
(defn- ref-attr?
[db attr]
(= :db.type/ref (get-in (d/schema db) [attr :db/valueType])))
(defn- update-op-av-coll
[db-before db-after a->add?->v->t]
(mapcat
(fn [[a add?->v->t]]
(mapcat
(fn [[add? v->t]]
(keep
(fn [[v t]]
(let [ref? (ref-attr? db-after a)]
(case [add? ref?]
[true true]
(when-let [v-uuid (:block/uuid (d/entity db-after v))]
[a v-uuid t add?])
[false true]
(when-let [v-uuid (:block/uuid
(or (d/entity db-after v)
(d/entity db-before v)))]
[a v-uuid t add?])
([true false] [false false]) [a (ldb/write-transit-str v) t add?])))
v->t))
add?->v->t))
a->add?->v->t))
(defn- redundant-update-op-av-coll?
[av-coll]
(every? (fn [av] (keyword-identical? :block/updated-at (first av))) av-coll))
(defn- max-t
[a->add?->v->t]
(apply max (mapcat vals (mapcat vals (vals a->add?->v->t)))))
(defn- get-first-vt
[add?->v->t k]
(some-> add?->v->t (get k) first))
(defn- entity-datoms=>ops
[db-before db-after e->a->add?->v->t ignore-attr-set entity-datoms]
(let [e (ffirst entity-datoms)
entity (d/entity db-after e)
{block-uuid :block/uuid} entity
a->add?->v->t (e->a->add?->v->t e)
{add?->block-name->t :block/name
add?->block-title->t :block/title
add?->block-uuid->t :block/uuid
add?->block-parent->t :block/parent
add?->block-order->t :block/order}
a->add?->v->t
[retract-block-uuid t1] (some-> add?->block-uuid->t (get false) first)
[retract-block-name _] (some-> add?->block-name->t (get false) first)
[add-block-name t2] (some-> add?->block-name->t latest-add?->v->t (get-first-vt true))
[add-block-title t3] (some-> add?->block-title->t latest-add?->v->t (get-first-vt true))
[add-block-parent t4] (some-> add?->block-parent->t latest-add?->v->t (get-first-vt true))
[add-block-order t5] (some-> add?->block-order->t latest-add?->v->t (get-first-vt true))
a->add?->v->t* (into {}
(filter
(fn [[a _]]
(and (watched-attr? a)
(not (contains? ignore-attr-set a)))))
a->add?->v->t)]
(cond
(and retract-block-uuid retract-block-name)
[[:remove-page t1 {:block-uuid retract-block-uuid}]]
retract-block-uuid
[[:remove t1 {:block-uuid retract-block-uuid}]]
:else
(let [ops (cond-> []
(or add-block-parent add-block-order)
(conj [:move (or t4 t5) {:block-uuid block-uuid}])
(or add-block-name
(and (ldb/page? entity) add-block-title))
(conj [:update-page (or t2 t3) {:block-uuid block-uuid}]))
update-op (when-let [av-coll (not-empty (update-op-av-coll db-before db-after a->add?->v->t*))]
(when-not (redundant-update-op-av-coll? av-coll)
(let [t (max-t a->add?->v->t*)]
[:update t {:block-uuid block-uuid :av-coll av-coll}])))]
(cond-> ops update-op (conj update-op))))))
(defn entity-datoms=>a->add?->v->t
[entity-datoms]
(reduce
(fn [m datom]
(let [[_e a v t add?] datom]
(assoc-in m [a add? v] t)))
{} entity-datoms))
(defn generate-rtc-ops
[db-before db-after same-entity-datoms-coll e->a->v->add?->t]
(mapcat
(partial entity-datoms=>ops
db-before db-after e->a->v->add?->t rtc-const/ignore-attrs-when-syncing)
same-entity-datoms-coll))
(defn- generate-rtc-ops-from-entities
[ents]
(let [db (d/entity-db (first ents))
id->same-entity-datoms
(into {}
(map (fn [ent]
(let [e (:db/id ent)
datoms (d/datoms db :eavt e)]
[e datoms])))
ents)
e->a->v->add?->t (update-vals id->same-entity-datoms entity-datoms=>a->add?->v->t)]
(generate-rtc-ops db db (vals id->same-entity-datoms) e->a->v->add?->t)))
(defn generate-rtc-ops-from-property-entities
[property-ents]
(when (seq property-ents)
(assert (every? ldb/property? property-ents))
(generate-rtc-ops-from-entities property-ents)))
(defn generate-rtc-ops-from-class-entities
[class-ents]
(when (seq class-ents)
(assert (every? ldb/class? class-ents))
(generate-rtc-ops-from-entities class-ents)))

View File

@@ -25,6 +25,11 @@
(def to-ws-op-schema
[:multi {:dispatch first :decode/string #(update % 0 keyword)}
[:update-kv-value
[:cat :keyword
[:map
[:db-ident :keyword]
[:value :string]]]]
[:move
[:cat :keyword
[:map
@@ -356,4 +361,4 @@
(def data-to-ws-coercer (m/coercer data-to-ws-schema mt/string-transformer nil
#(do
(log/error ::data-to-ws-schema %)
(m/-fail! ::data-to-ws-schema %))))
(m/-fail! ::data-to-ws-schema (select-keys % [:value])))))

View File

@@ -0,0 +1,49 @@
(ns frontend.worker.rtc.migrate
"migrate server data according to schema-version and client's migration-updates"
(:require [datascript.core :as d]
[frontend.worker.db.migrate :as db-migrate]
[frontend.worker.rtc.client-op :as client-op]
[frontend.worker.rtc.gen-client-op :as gen-client-op]
[logseq.db.frontend.schema :as db-schema]))
(defn- server-client-schema-version->migrations
[server-schema-version client-schema-version]
(when (neg? (db-schema/compare-schema-version server-schema-version client-schema-version))
(let [sorted-schema-version->updates
(->> (map (fn [[schema-version updates]]
[((juxt :major :minor) (db-schema/parse-schema-version schema-version))
updates])
db-migrate/schema-version->updates)
(sort-by first))]
(->> sorted-schema-version->updates
(drop-while (fn [[schema-version _updates]]
(not (neg? (db-schema/compare-schema-version server-schema-version schema-version)))))
(take-while (fn [[schema-version _updates]]
(not (neg? (db-schema/compare-schema-version client-schema-version schema-version)))))
(map second)))))
(defn- migration-updates->client-ops
"convert :classes, :properties from frontend.worker.db.migrate/schema-version->updates into client-ops"
[db client-schema-version migrate-updates]
(let [property-ks (mapcat :properties migrate-updates)
class-ks (mapcat :classes migrate-updates)
d-entity-fn (partial d/entity db)
new-property-entities (keep d-entity-fn property-ks)
new-class-entities (keep d-entity-fn class-ks)
client-ops (vec (concat (gen-client-op/generate-rtc-ops-from-property-entities new-property-entities)
(gen-client-op/generate-rtc-ops-from-class-entities new-class-entities)))
max-t (apply max 0 (map second client-ops))]
(conj client-ops
[:update-kv-value
max-t
{:db-ident :logseq.kv/schema-version
:value client-schema-version}])))
(defn add-migration-client-ops!
[repo db server-schema-version client-schema-version]
(assert (and server-schema-version client-schema-version))
(when-let [ops (not-empty
(some->> (server-client-schema-version->migrations server-schema-version client-schema-version)
(migration-updates->client-ops db client-schema-version)))]
(client-op/add-ops! repo ops)
ops))

View File

@@ -317,10 +317,10 @@ so need to pull earlier remote-data from websocket."})
(defn- affected-blocks->diff-type-ops
[repo affected-blocks]
(let [unpushed-ops (client-op/get-all-block-ops repo)
affected-blocks-map* (if unpushed-ops
(let [unpushed-block-ops (client-op/get-all-block-ops repo)
affected-blocks-map* (if unpushed-block-ops
(update-remote-data-by-local-unpushed-ops
affected-blocks unpushed-ops)
affected-blocks unpushed-block-ops)
affected-blocks)
{remove-ops-map :remove move-ops-map :move update-ops-map :update-attrs
move+update-ops-map :move+update-attrs

View File

@@ -51,4 +51,5 @@
(conj [:p (str :client-only-db-idents client-only)])
(seq server-only)
(conj [:p (str :server-only-db-idents server-only)]))
:error]))))))))
:error])))
r)))))

View File

@@ -534,8 +534,8 @@
:command.editor/delete "向右删除"
:command.editor/cycle-todo "切换TODO状态"
:command.editor/clear-block "清除块内容"
:command.editor/kill-line-before "删除光标侧行"
:command.editor/kill-line-after "删除光标侧行"
:command.editor/kill-line-before "删除光标侧行"
:command.editor/kill-line-after "删除光标侧行"
:command.editor/beginning-of-block "移动光标到块开始位置"
:command.editor/end-of-block "移动光标到块末尾"
:command.editor/forward-word "光标向后移动一个单词"

View File

@@ -40,7 +40,25 @@
(testing "user.property/xxx creation"
(let [block-uuid (random-uuid)
block-order "b0P"
db (d/db-with empty-db [{:db/index true
db (d/db-with empty-db [{:block/uuid #uuid "00000002-5389-0208-3000-000000000000",
:block/updated-at 1741424828774,
:block/created-at 1741424828774,
:logseq.property/built-in? true,
:block/tags [2],
:block/title "Tag",
:db/id 2,
:db/ident :logseq.class/Tag,
:block/name "tag"}
{:block/uuid #uuid "00000002-1038-7670-4800-000000000000",
:block/updated-at 1741424828774,
:block/created-at 1741424828774,
:logseq.property/built-in? true,
:block/tags [2]
:block/title "Property",
:db/id 3,
:db/ident :logseq.class/Property,
:block/name "property"}
{:db/index true
:block/uuid block-uuid
:db/valueType :db.type/ref
:block/updated-at 1716880036491
@@ -48,6 +66,7 @@
:logseq.property/type :number
:db/cardinality :db.cardinality/one
:db/ident :user.property/xxx,
:block/tags [3]
:block/type "property",
:block/order block-order,
:block/name "xxx",

View File

@@ -4,6 +4,7 @@
[frontend.test.helper :as test-helper]
[frontend.worker.db-listener :as worker-db-listener]
[frontend.worker.rtc.client-op :as client-op]
[frontend.worker.rtc.db-listener]
[frontend.worker.state :as worker-state]))
(def listen-test-db-to-gen-rtc-ops-fixture

View File

@@ -1,17 +1,19 @@
(ns frontend.worker.rtc.db-listener-test
(ns frontend.worker.rtc.gen-client-op-test
(:require [cljs.test :as t :refer [deftest is testing]]
[clojure.set :as set]
[datascript.core :as d]
[frontend.db.conn :as conn]
[frontend.state :as state]
[frontend.test.helper :as test-helper]
[frontend.worker.handler.page :as worker-page]
[frontend.worker.rtc.client-op :as client-op]
[frontend.worker.rtc.db-listener :as subject]
[frontend.worker.rtc.fixture :as r.fixture]
[frontend.worker.rtc.gen-client-op :as subject]
[frontend.worker.state :as worker-state]
[logseq.db.test.helper :as db-test]
[logseq.outliner.batch-tx :as batch-tx]
[logseq.outliner.core :as outliner-core]))
[logseq.outliner.core :as outliner-core]
[meander.epsilon :as me]))
(t/use-fixtures :each
test-helper/db-based-start-and-destroy-db-map-fixture
@@ -156,3 +158,35 @@
(is (=
{block-uuid1 #{:remove}}
(ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo))))))))
(deftest generate-rtc-ops-from-property-entity-test
(let [repo (state/get-current-repo)
db (conn/get-db repo true)
ent (d/entity db :logseq.property.view/feature-type)
av-coll-attrs #{:logseq.property/type :logseq.property/built-in?
:logseq.property/public? :logseq.property/hide?
:block/tags :block/title :db/cardinality}]
#_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]}
(is (->> (me/find (subject/generate-rtc-ops-from-property-entities [ent])
([:move _ {:block-uuid ?block-uuid}]
[:update-page _ {:block-uuid ?block-uuid}]
[:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
!av-coll-attrs)
set
(set/difference av-coll-attrs)
empty?))))
(deftest generate-rtc-ops-from-class-entity-test
(let [repo (state/get-current-repo)
db (conn/get-db repo true)
ent (d/entity db :logseq.class/Template)
av-coll-attrs #{:logseq.property.class/properties :logseq.property/built-in? :logseq.property/parent
:block/tags :block/title}]
#_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]}
(is (->> (me/find (subject/generate-rtc-ops-from-class-entities [ent])
([:update-page _ {:block-uuid ?block-uuid}]
[:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
!av-coll-attrs)
set
(set/difference av-coll-attrs)
empty?))))

View File

@@ -561,6 +561,13 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
"@isaacs/fs-minipass@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
dependencies:
minipass "^7.0.4"
"@logseq/rsapi-darwin-arm64@0.0.91":
version "0.0.91"
resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-arm64/-/rsapi-darwin-arm64-0.0.91.tgz#e29cd37a372609b04d7dcfc7ae4c1405c7a54353"
@@ -648,10 +655,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@npmcli/agent@^2.0.0":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.2.tgz#967604918e62f620a648c7975461c9c9e74fc5d5"
integrity sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==
"@npmcli/agent@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-3.0.0.tgz#1685b1fbd4a1b7bb4f930cbb68ce801edfe7aa44"
integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==
dependencies:
agent-base "^7.1.0"
http-proxy-agent "^7.0.0"
@@ -659,10 +666,10 @@
lru-cache "^10.0.1"
socks-proxy-agent "^8.0.3"
"@npmcli/fs@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.1.tgz#59cdaa5adca95d135fc00f2bb53f5771575ce726"
integrity sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==
"@npmcli/fs@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-4.0.0.tgz#a1eb1aeddefd2a4a347eca0fab30bc62c0e1c0f2"
integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==
dependencies:
semver "^7.3.5"
@@ -845,12 +852,12 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.23.tgz#75c580983846181ebe5f4abc40fe9dfb2d65665f"
integrity sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==
"@types/node@^20.9.0":
version "20.17.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.8.tgz#42748cdb169adf5be7c9760604c72820c7b7d560"
integrity sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==
"@types/node@^22.7.7":
version "22.13.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
dependencies:
undici-types "~6.19.2"
undici-types "~6.20.0"
"@types/plist@^3.0.1":
version "3.0.2"
@@ -884,10 +891,10 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
abbrev@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
abbrev@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-3.0.0.tgz#c29a6337e167ac61a84b41b80461b29c5c271a27"
integrity sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==
abort-controller@3.0.0:
version "3.0.0"
@@ -1351,12 +1358,12 @@ builder-util@25.1.7:
stat-mode "^1.0.0"
temp-file "^3.4.0"
cacache@^18.0.0:
version "18.0.4"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.4.tgz#4601d7578dadb59c66044e157d02a3314682d6a5"
integrity sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==
cacache@^19.0.1:
version "19.0.1"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-19.0.1.tgz#3370cc28a758434c85c2585008bd5bdcff17d6cd"
integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==
dependencies:
"@npmcli/fs" "^3.1.0"
"@npmcli/fs" "^4.0.0"
fs-minipass "^3.0.0"
glob "^10.2.2"
lru-cache "^10.0.1"
@@ -1364,10 +1371,10 @@ cacache@^18.0.0:
minipass-collect "^2.0.1"
minipass-flush "^1.0.5"
minipass-pipeline "^1.2.4"
p-map "^4.0.0"
ssri "^10.0.0"
tar "^6.1.11"
unique-filename "^3.0.0"
p-map "^7.0.2"
ssri "^12.0.0"
tar "^7.4.3"
unique-filename "^4.0.0"
cacheable-lookup@^5.0.3:
version "5.0.4"
@@ -1415,6 +1422,11 @@ chownr@^2.0.0:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
chownr@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
chrome-trace-event@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -1997,13 +2009,13 @@ electron-wix-msi@^5.1.3:
optionalDependencies:
"@bitdisaster/exe-icon-extractor" "^1.0.10"
electron@*, electron@31.7.5:
version "31.7.5"
resolved "https://registry.yarnpkg.com/electron/-/electron-31.7.5.tgz#98396c75041808b26c7b6a8844cbedacee93d8a6"
integrity sha512-8zFzVJdhxTRmoPcRiKkEmPW0bJHAUsTQJwEX2YJ8X0BVFIJLwSvHkSlpCjEExVbNCAk+gHnkIYX+2OyCXrRwHQ==
electron@*, electron@35.0.1:
version "35.0.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-35.0.1.tgz#8596e3936c4b5787e0cf52739bf6842925cfcdc1"
integrity sha512-iQonj6lnPhqfqha2KXx6LzV1dnu6UPTCWK+b7f9Zvg828umGemi22DKbcJ3/q+Opn7iUVTWyqp9z1JQqkIi6OA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"
"@types/node" "^22.7.7"
extract-zip "^2.0.1"
emoji-regex@^8.0.0:
@@ -2540,7 +2552,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
glob@^10.2.2, glob@^10.3.12:
glob@^10.2.2, glob@^10.3.12, glob@^10.3.7:
version "10.4.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
@@ -2900,11 +2912,6 @@ is-interactive@^1.0.0:
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
is-lambda@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==
is-my-ip-valid@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442"
@@ -3294,23 +3301,22 @@ macos-alias@~0.2.5:
dependencies:
nan "^2.4.0"
make-fetch-happen@^13.0.0:
version "13.0.1"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36"
integrity sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==
make-fetch-happen@^14.0.3:
version "14.0.3"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz#d74c3ecb0028f08ab604011e0bc6baed483fcdcd"
integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==
dependencies:
"@npmcli/agent" "^2.0.0"
cacache "^18.0.0"
"@npmcli/agent" "^3.0.0"
cacache "^19.0.1"
http-cache-semantics "^4.1.1"
is-lambda "^1.0.1"
minipass "^7.0.2"
minipass-fetch "^3.0.0"
minipass-fetch "^4.0.0"
minipass-flush "^1.0.5"
minipass-pipeline "^1.2.4"
negotiator "^0.6.3"
proc-log "^4.2.0"
negotiator "^1.0.0"
proc-log "^5.0.0"
promise-retry "^2.0.1"
ssri "^10.0.0"
ssri "^12.0.0"
map-age-cleaner@^0.1.1:
version "0.1.3"
@@ -3439,14 +3445,14 @@ minipass-collect@^2.0.1:
dependencies:
minipass "^7.0.3"
minipass-fetch@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.5.tgz#f0f97e40580affc4a35cc4a1349f05ae36cb1e4c"
integrity sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==
minipass-fetch@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.1.tgz#f2d717d5a418ad0b1a7274f9b913515d3e78f9e5"
integrity sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==
dependencies:
minipass "^7.0.3"
minipass-sized "^1.0.3"
minizlib "^2.1.2"
minizlib "^3.0.1"
optionalDependencies:
encoding "^0.1.13"
@@ -3488,12 +3494,12 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.2:
minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^2.1.1, minizlib@^2.1.2:
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
@@ -3501,6 +3507,14 @@ minizlib@^2.1.1, minizlib@^2.1.2:
minipass "^3.0.0"
yallist "^4.0.0"
minizlib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012"
integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==
dependencies:
minipass "^7.0.4"
rimraf "^5.0.5"
mkdirp@^0.5.1:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
@@ -3513,6 +3527,11 @@ mkdirp@^1.0.3:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
mnemonist@0.39.8:
version "0.39.8"
resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38"
@@ -3554,20 +3573,20 @@ nan@^2.4.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
negotiator@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
negotiator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-abi@3.68.0, node-abi@^3.0.0, node-abi@^3.45.0:
version "3.68.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.68.0.tgz#8f37fb02ecf4f43ebe694090dcb52e0c4cc4ba25"
integrity sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==
node-abi@3.74.0, node-abi@^3.0.0, node-abi@^3.45.0:
version "3.74.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0"
integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==
dependencies:
semver "^7.3.5"
@@ -3612,28 +3631,28 @@ node-gyp-build@^4.2.1:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40"
integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==
node-gyp@10.0.1, node-gyp@^9.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.0.1.tgz#205514fc19e5830fa991e4a689f9e81af377a966"
integrity sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==
node-gyp@11.1.0, node-gyp@^9.0.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-11.1.0.tgz#212a1d9c167c50d727d42659410780b40e07bbd3"
integrity sha512-/+7TuHKnBpnMvUQnsYEb0JOozDZqarQbfNuSGLXIjhStMT0fbw7IdSqWgopOP5xhRZE+lsbIvAHcekddruPZgQ==
dependencies:
env-paths "^2.2.0"
exponential-backoff "^3.1.1"
glob "^10.3.10"
graceful-fs "^4.2.6"
make-fetch-happen "^13.0.0"
nopt "^7.0.0"
proc-log "^3.0.0"
make-fetch-happen "^14.0.3"
nopt "^8.0.0"
proc-log "^5.0.0"
semver "^7.3.5"
tar "^6.1.2"
which "^4.0.0"
tar "^7.4.3"
which "^5.0.0"
nopt@^7.0.0:
version "7.2.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7"
integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
nopt@^8.0.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-8.1.0.tgz#b11d38caf0f8643ce885818518064127f602eae3"
integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==
dependencies:
abbrev "^2.0.0"
abbrev "^3.0.0"
normalize-package-data@^2.3.2:
version "2.5.0"
@@ -3791,6 +3810,11 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
p-map@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6"
integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -3987,15 +4011,10 @@ postject@^1.0.0-alpha.6:
dependencies:
commander "^9.4.0"
proc-log@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8"
integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==
proc-log@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034"
integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==
proc-log@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==
process-nextick-args@~2.0.0:
version "2.0.1"
@@ -4278,6 +4297,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rimraf@^5.0.5:
version "5.0.10"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
dependencies:
glob "^10.3.7"
rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -4599,10 +4625,10 @@ sprintf-js@^1.1.3:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
ssri@^10.0.0:
version "10.0.6"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.6.tgz#a8aade2de60ba2bce8688e3fa349bad05c7dc1e5"
integrity sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==
ssri@^12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832"
integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==
dependencies:
minipass "^7.0.3"
@@ -4625,7 +4651,7 @@ stream-buffers@~2.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.0, string-width@^5.1.2:
string-width@4.2.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.0, string-width@^5.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
@@ -4703,7 +4729,7 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tar@^6.0.5, tar@^6.1.11, tar@^6.1.2:
tar@^6.0.5, tar@^6.1.11:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
@@ -4727,6 +4753,18 @@ tar@^6.1.12:
mkdirp "^1.0.3"
yallist "^4.0.0"
tar@^7.4.3:
version "7.4.3"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
minipass "^7.1.2"
minizlib "^3.0.1"
mkdirp "^3.0.1"
yallist "^5.0.0"
temp-file@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
@@ -4857,22 +4895,22 @@ typescript@^5.4.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
unique-filename@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea"
integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==
dependencies:
unique-slug "^4.0.0"
unique-slug@^4.0.0:
unique-filename@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3"
integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-4.0.0.tgz#a06534d370e7c977a939cd1d11f7f0ab8f1fed13"
integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==
dependencies:
unique-slug "^5.0.0"
unique-slug@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-5.0.0.tgz#ca72af03ad0dbab4dad8aa683f633878b1accda8"
integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==
dependencies:
imurmurhash "^0.1.4"
@@ -4994,10 +5032,10 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
which@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a"
integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==
which@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6"
integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==
dependencies:
isexe "^3.1.1"
@@ -5054,6 +5092,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yallist@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"

2725
yarn.lock

File diff suppressed because it is too large Load Diff