diff --git a/deps/db/src/logseq/db/frontend/property.cljs b/deps/db/src/logseq/db/frontend/property.cljs index 9d02308b1c..c57e65bc1f 100644 --- a/deps/db/src/logseq/db/frontend/property.cljs +++ b/deps/db/src/logseq/db/frontend/property.cljs @@ -26,7 +26,7 @@ * :cardinality - property cardinality. Default to one/single cardinality if not set * :hide? - Boolean which hides property when set on a block or exported e.g. slides * :public? - Boolean which allows property to be used by user: add and remove property to blocks/pages - and queryable via property and has-property rules + and queryable via property and has-property rules. When it's not set, it's the same as false * :view-context - Keyword to indicate which view contexts a property can be seen in when :public? is set. Valid values are :page, :block and :never. Property can be viewed in any context if not set @@ -662,6 +662,11 @@ :public? false :hide? true}}))) +(def public-built-in-properties + (->> built-in-properties + (keep (fn [[k v]] (when (get-in v [:schema :public?]) k))) + set)) + (def db-attribute-properties "Internal properties that are also db schema attributes" #{:block/alias :block/tags :block/parent diff --git a/src/main/logseq/cli/command/show.cljs b/src/main/logseq/cli/command/show.cljs index fa39393caf..426a6a69ae 100644 --- a/src/main/logseq/cli/command/show.cljs +++ b/src/main/logseq/cli/command/show.cljs @@ -9,7 +9,9 @@ [logseq.cli.style :as style] [logseq.cli.transport :as transport] [logseq.common.util :as common-util] - [promesa.core :as p])) + [promesa.core :as p] + [clojure.set :as set] + [logseq.db.frontend.property :as db-property])) (def ^:private show-spec {:id {:desc "Block db/id or EDN vector of ids"} @@ -154,12 +156,21 @@ (when (seq labels) (string/join " " (map #(style/bold (str "#" %)) labels))))) -(def ^:private user-property-namespace "user.property") +(def ^:private displayable-built-in-properties + "Built-in properties that are displayed alongside user properties." + (set/difference db-property/public-built-in-properties + ;; Exclude built-in properties handled elsewhere in this command + #{:block/tags :logseq.property/status})) (defn- user-property-key? [k] (and (qualified-keyword? k) - (= user-property-namespace (namespace k)))) + (= db-property/default-user-namespace (namespace k)))) + +(defn- displayable-property-key? + [k] + (or (user-property-key? k) + (contains? displayable-built-in-properties k))) (defn- nonblank-string [value] @@ -223,7 +234,7 @@ ([node] (node-user-property-entries node nil)) ([node labels] (->> node - (filter (fn [[k _]] (user-property-key? k))) + (filter (fn [[k _]] (displayable-property-key? k))) (map (fn [[k v]] [k (normalize-property-values v labels)])) (remove (fn [[_ values]] (empty? values))) vec))) @@ -488,7 +499,7 @@ [{:keys [root linked-references]}] (letfn [(collect-node [node] (let [node-keys (->> (keys node) - (filter user-property-key?))] + (filter displayable-property-key?))] (reduce (fn [acc child] (into acc (collect-node child))) (set node-keys) @@ -542,7 +553,7 @@ :else acc)) (collect-node [acc node] (let [acc (reduce (fn [acc [k v]] - (if (user-property-key? k) + (if (displayable-property-key? k) (collect-value acc v) acc)) acc @@ -633,12 +644,21 @@ [(namespace ?a) ?ns] [(= "user.property" ?ns)] [(get-else $ ?e :logseq.property/type :default) ?type]] + built-in-query '[:find ?a ?type + :in $ [?a ...] + :where + [?e :db/ident ?a] + [(get-else $ ?e :logseq.property/type :default) ?type]] props-query '[:find ?b ?a ?v :in $ [?b ...] [?a ...] :where [?b ?a ?v]] - ids (vec block-ids)] - (p/let [ident-type-pairs (transport/invoke config :thread-api/q false [repo [idents-query]]) + ids (vec block-ids) + built-in-idents (vec displayable-built-in-properties)] + (p/let [user-pairs (transport/invoke config :thread-api/q false [repo [idents-query]]) + built-in-pairs (transport/invoke config :thread-api/q false + [repo [built-in-query built-in-idents]]) + ident-type-pairs (into (vec user-pairs) built-in-pairs) datetime-idents (set (keep (fn [[a type]] (when (= :datetime type) a)) ident-type-pairs)) property-idents (vec (map first ident-type-pairs))] (if (seq property-idents) diff --git a/src/test/logseq/cli/command/show_test.cljs b/src/test/logseq/cli/command/show_test.cljs index 2b9acdaf7c..f275bdf807 100644 --- a/src/test/logseq/cli/command/show_test.cljs +++ b/src/test/logseq/cli/command/show_test.cljs @@ -101,12 +101,14 @@ (let [call-idx (swap! call-count inc)] (p/resolved (case call-idx - ;; First call: idents-query returns property idents with types + ;; First call: user idents-query returns property idents with types 1 [[:user.property/title :default] [:user.property/due :datetime] [:user.property/count :number]] - ;; Second call: props-query returns raw values - 2 [[10 :user.property/title "hello"] + ;; Second call: built-in idents-query returns built-in property types + 2 [] + ;; Third call: props-query returns raw values + 3 [[10 :user.property/title "hello"] [10 :user.property/due 1774267200000] [10 :user.property/count 42]] []))))] @@ -123,6 +125,37 @@ (p/catch (fn [e] (is false (str "unexpected error: " e)))) (p/finally done))))) +(deftest test-fetch-user-properties-includes-built-in-datetime + (let [fetch #'show-command/fetch-user-properties + call-count (atom 0) + mock-invoke (fn [_ _method _ _args] + (let [call-idx (swap! call-count inc)] + (p/resolved + (case call-idx + ;; First call: user idents-query + 1 [[:user.property/status :default]] + ;; Second call: built-in idents-query returns deadline and scheduled + 2 [[:logseq.property/deadline :datetime] + [:logseq.property/scheduled :datetime]] + ;; Third call: props-query returns raw values + 3 [[10 :user.property/status "todo"] + [10 :logseq.property/deadline 1774267200000] + [10 :logseq.property/scheduled 1774180800000]] + []))))] + (async done + (-> (p/with-redefs [transport/invoke mock-invoke] + (p/let [result (fetch {} "demo" [10])] + (testing "built-in deadline is converted to ISO string" + (is (string? (get-in result [10 :logseq.property/deadline]))) + (is (string/includes? (get-in result [10 :logseq.property/deadline]) "2026"))) + (testing "built-in scheduled is converted to ISO string" + (is (string? (get-in result [10 :logseq.property/scheduled]))) + (is (string/includes? (get-in result [10 :logseq.property/scheduled]) "2026"))) + (testing "user property is unaffected" + (is (= "todo" (get-in result [10 :user.property/status])))))) + (p/catch (fn [e] (is false (str "unexpected error: " e)))) + (p/finally done))))) + (defn- call-private [sym & args] (when-let [v (get (ns-interns 'logseq.cli.command.show) sym)]