|
|
@@ -1,169 +1,188 @@
|
|
|
(ns logseq.db.frontend.property
|
|
|
"Property related fns for DB graphs and frontend/datascript usage"
|
|
|
- (:require [clojure.set :as set]
|
|
|
- [logseq.db.sqlite.util :as sqlite-util]
|
|
|
+ (:require [logseq.db.sqlite.util :as sqlite-util]
|
|
|
[datascript.core :as d]
|
|
|
[logseq.common.util :as common-util]
|
|
|
[clojure.string :as string]))
|
|
|
|
|
|
-;; FIXME: no support for built-in-extended-properties
|
|
|
-(def ^:large-vars/data-var built-in-properties
|
|
|
- "Map of built in properties for db graphs. Each property has a config map with
|
|
|
- the following keys:
|
|
|
- * :schema - Property's schema. Required key
|
|
|
+(def ^:large-vars/data-var built-in-properties*
|
|
|
+ "Map of built in properties for db graphs with their :db/ident as keys.
|
|
|
+ Each property has a config map with the following keys:
|
|
|
+ * :schema - Property's schema. Required key. Has the following common keys:
|
|
|
+ * :type - Property type
|
|
|
+ * :cardinality - property cardinality. Default to one/single cardinality if not set
|
|
|
+ * :hide? - Boolean which hides property when set on a block
|
|
|
+ * :public? - Boolean which allows property to be used by user e.g. add and remove property to blocks/pages
|
|
|
+ * :view-context - Keyword to indicate which view contexts a property can be
|
|
|
+ seen in when :public? is set. Valid values are :page and :block. Property can
|
|
|
+ be viewed in any context if not set
|
|
|
* :original-name - Property's :block/original-name
|
|
|
+ * :name - Property's :block/name as a keyword. If none given, one is derived from the db/ident
|
|
|
* :attribute - Property keyword that is saved to a datascript attribute outside of :block/properties
|
|
|
- * :visible - Boolean to indicate user can see and use this property"
|
|
|
- {:alias {:original-name "Alias"
|
|
|
- :attribute :block/alias
|
|
|
- :db-ident :block/alias
|
|
|
- :visible true
|
|
|
- :schema {:type :page
|
|
|
- :cardinality :many}}
|
|
|
- :tags {:original-name "Tags"
|
|
|
- :attribute :block/tags
|
|
|
- :db-ident :block/tags
|
|
|
- :visible true
|
|
|
- :schema {:type :page
|
|
|
- :cardinality :many
|
|
|
- :classes #{:logseq.class}}}
|
|
|
- :pagetags {:original-name "pageTags"
|
|
|
- :visible true
|
|
|
- :schema {:type :page
|
|
|
- :cardinality :many}}
|
|
|
- :background-color {:schema {:type :default :hide? true}}
|
|
|
- :background-image {:schema {:type :default :hide? true}
|
|
|
- :visible true}
|
|
|
- :heading {:schema {:type :any :hide? true}} ; number (1-6) or boolean for auto heading
|
|
|
- :created-from-block {:schema {:db-attr-type :ref
|
|
|
- :type :number}}
|
|
|
- :created-from-property {:schema {:db-attr-type :ref
|
|
|
- :type :number}}
|
|
|
- :created-from-template {:schema {:db-attr-type :ref
|
|
|
- :type :number}}
|
|
|
- :source-page-id {:schema {:type :ref}}
|
|
|
- :built-in? {:schema {:type :checkbox}}
|
|
|
- :hide-properties? {:schema {:type :checkbox}}
|
|
|
- :query-table {:schema {:type :checkbox}}
|
|
|
+ * :closed-values - Vec of closed-value maps for properties with choices. Map
|
|
|
+ has keys :value, :db-ident, :uuid and :icon
|
|
|
+ * :db-ident - Keyword to set :db/ident and give property unique id in db"
|
|
|
+ {:block/alias {:original-name "Alias"
|
|
|
+ :attribute :block/alias
|
|
|
+ :schema {:type :page
|
|
|
+ :cardinality :many
|
|
|
+ :view-context :page
|
|
|
+ :public? true}}
|
|
|
+ :block/tags {:original-name "Tags"
|
|
|
+ :attribute :block/tags
|
|
|
+ :schema {:type :page
|
|
|
+ :cardinality :many
|
|
|
+ :public? true
|
|
|
+ :classes #{:logseq.class}}}
|
|
|
+ :logseq.property/page-tags {:original-name "pageTags"
|
|
|
+ :schema {:type :page
|
|
|
+ :public? true
|
|
|
+ :view-context :page
|
|
|
+ :cardinality :many}}
|
|
|
+ :logseq.property/background-color {:schema {:type :default :hide? true}}
|
|
|
+ :logseq.property/background-image {:schema
|
|
|
+ {:type :default
|
|
|
+ :hide? true
|
|
|
+ :view-context :block
|
|
|
+ :public? true}}
|
|
|
+ ;; number (1-6) or boolean for auto heading
|
|
|
+ :logseq.property/heading {:schema {:type :any :hide? true}}
|
|
|
+ :logseq.property/created-from-block {:schema {:type :uuid}}
|
|
|
+ :logseq.property/created-from-property {:schema {:type :uuid}}
|
|
|
+ :logseq.property/created-from-template {:schema {:type :uuid}}
|
|
|
+ :logseq.property/source-page-id {:schema {:type :uuid}}
|
|
|
+ :logseq.property/built-in? {:schema {:type :checkbox}}
|
|
|
+ :logseq.property/hide-properties? {:schema {:type :checkbox}}
|
|
|
+ :logseq.property/query-table {:schema {:type :checkbox}}
|
|
|
;; query-properties is a coll of property uuids and keywords where keywords are special frontend keywords
|
|
|
- :query-properties {:schema {:type :coll}}
|
|
|
+ :logseq.property/query-properties {:schema {:type :coll}}
|
|
|
;; query-sort-by is either a property uuid or a keyword where keyword is a special frontend keyword
|
|
|
- :query-sort-by {:schema {:type :any}}
|
|
|
- :query-sort-desc {:schema {:type :checkbox}}
|
|
|
- :ls-type {:schema {:type :keyword}}
|
|
|
- :hl-type {:schema {:type :keyword}}
|
|
|
- :hl-page {:schema {:type :number}}
|
|
|
- :hl-stamp {:schema {:type :number}}
|
|
|
- :hl-color {:schema {:type :default}}
|
|
|
- :logseq.macro-name {:schema {:type :default}}
|
|
|
- :logseq.macro-arguments {:schema {:type :coll}}
|
|
|
- :logseq.order-list-type {:schema {:type :default}}
|
|
|
- :logseq.tldraw.page {:schema {:type :map}}
|
|
|
- :logseq.tldraw.shape {:schema {:type :map}}
|
|
|
+ :logseq.property/query-sort-by {:schema {:type :any}}
|
|
|
+ :logseq.property/query-sort-desc {:schema {:type :checkbox}}
|
|
|
+ :logseq.property/ls-type {:schema {:type :keyword}}
|
|
|
+ :logseq.property/hl-type {:schema {:type :keyword}}
|
|
|
+ :logseq.property/hl-page {:schema {:type :number}}
|
|
|
+ :logseq.property/hl-stamp {:schema {:type :number}}
|
|
|
+ :logseq.property/hl-color {:schema {:type :default}}
|
|
|
+ :logseq.property/macro-name {:name :logseq.macro-name
|
|
|
+ :schema {:type :default}}
|
|
|
+ :logseq.property/macro-arguments {:name :logseq.macro-arguments
|
|
|
+ :schema {:type :coll}}
|
|
|
+ :logseq.property/order-list-type {:name :logseq.order-list-type
|
|
|
+ :schema {:type :default}}
|
|
|
+ :logseq.property.tldraw/page {:name :logseq.tldraw.page
|
|
|
+ :schema {:type :map}}
|
|
|
+ :logseq.property.tldraw/shape {:name :logseq.tldraw.shape
|
|
|
+ :schema {:type :map}}
|
|
|
|
|
|
;; Task props
|
|
|
- :status {:db-ident :logseq.property/status
|
|
|
- :original-name "Status"
|
|
|
- :schema
|
|
|
- {:type :default}
|
|
|
- :closed-values
|
|
|
- (mapv (fn [[db-ident value icon]]
|
|
|
- {:db-ident db-ident
|
|
|
- :value value
|
|
|
- :uuid (random-uuid)
|
|
|
- :icon {:type :tabler-icon :id icon :name icon}})
|
|
|
- [[:logseq.property/status.backlog "Backlog" "Backlog"]
|
|
|
- [:logseq.property/status.todo "Todo" "Todo"]
|
|
|
- [:logseq.property/status.doing "Doing" "InProgress50"]
|
|
|
- [:logseq.property/status.in-review "In Review" "InReview"]
|
|
|
- [:logseq.property/status.done "Done" "Done"]
|
|
|
- [:logseq.property/status.canceled "Canceled" "Cancelled"]])
|
|
|
- :visible true}
|
|
|
- :priority {:db-ident :logseq.property/priority
|
|
|
- :original-name "Priority"
|
|
|
- :schema
|
|
|
- {:type :default}
|
|
|
- :closed-values
|
|
|
- (mapv (fn [[db-ident value]]
|
|
|
- {:db-ident db-ident
|
|
|
- :value value
|
|
|
- :uuid (random-uuid)})
|
|
|
- [[:logseq.property/priority.urgent "Urgent"]
|
|
|
- [:logseq.property/priority.high "High"]
|
|
|
- [:logseq.property/priority.medium "Medium"]
|
|
|
- [:logseq.property/priority.low "Low"]])
|
|
|
- :visible true}
|
|
|
- :scheduled {:db-ident :logseq.property/scheduled
|
|
|
- :original-name "Scheduled"
|
|
|
- :schema {:db-attr-type :ref
|
|
|
- :type :date}
|
|
|
- :visible true}
|
|
|
- :deadline {:db-ident :logseq.property/deadline
|
|
|
- :original-name "Deadline"
|
|
|
- :schema {:db-attr-type :ref
|
|
|
- :type :date}
|
|
|
- :visible true}
|
|
|
+ :logseq.task/status
|
|
|
+ {:original-name "Status"
|
|
|
+ :schema
|
|
|
+ {:type :default
|
|
|
+ :public? true}
|
|
|
+ :closed-values
|
|
|
+ (mapv (fn [[db-ident value icon]]
|
|
|
+ {:db-ident db-ident
|
|
|
+ :value value
|
|
|
+ :uuid (random-uuid)
|
|
|
+ :icon {:type :tabler-icon :id icon :name icon}})
|
|
|
+ [[:logseq.task/status.backlog "Backlog" "Backlog"]
|
|
|
+ [:logseq.task/status.todo "Todo" "Todo"]
|
|
|
+ [:logseq.task/status.doing "Doing" "InProgress50"]
|
|
|
+ [:logseq.task/status.in-review "In Review" "InReview"]
|
|
|
+ [:logseq.task/status.done "Done" "Done"]
|
|
|
+ [:logseq.task/status.canceled "Canceled" "Cancelled"]])}
|
|
|
+ :logseq.task/priority
|
|
|
+ {:original-name "Priority"
|
|
|
+ :schema
|
|
|
+ {:type :default
|
|
|
+ :public? true}
|
|
|
+ :closed-values
|
|
|
+ (mapv (fn [[db-ident value]]
|
|
|
+ {:db-ident db-ident
|
|
|
+ :value value
|
|
|
+ :uuid (random-uuid)})
|
|
|
+ [[:logseq.task/priority.urgent "Urgent"]
|
|
|
+ [:logseq.task/priority.high "High"]
|
|
|
+ [:logseq.task/priority.medium "Medium"]
|
|
|
+ [:logseq.task/priority.low "Low"]])}
|
|
|
+ :logseq.task/scheduled
|
|
|
+ {:original-name "Scheduled"
|
|
|
+ :schema {:type :date
|
|
|
+ :public? true}}
|
|
|
+ :logseq.task/deadline
|
|
|
+ {:original-name "Deadline"
|
|
|
+ :schema {:type :date
|
|
|
+ :public? true}}
|
|
|
|
|
|
;; TODO: Add more props :Assignee, :Estimate, :Cycle, :Project
|
|
|
|
|
|
;; color props
|
|
|
- :logseq.color {:schema
|
|
|
- {:type :default :hide? true}
|
|
|
- :closed-values
|
|
|
- (mapv #(hash-map :db-ident (keyword "logseq.property" (str "color." %))
|
|
|
- :value %
|
|
|
- :uuid (random-uuid))
|
|
|
- ;; Stringified version of frontend.colors/COLORS. Too basic to couple
|
|
|
- ["tomato" "red" "crimson" "pink" "plum" "purple" "violet" "indigo" "blue" "cyan" "teal" "green" "grass" "orange" "brown"])
|
|
|
- :visible true}
|
|
|
+ :logseq.property/color
|
|
|
+ {:name :logseq.color
|
|
|
+ :schema
|
|
|
+ {:type :default :hide? true :public? true}
|
|
|
+ :closed-values
|
|
|
+ (mapv #(hash-map :db-ident (keyword "logseq.property" (str "color." %))
|
|
|
+ :value %
|
|
|
+ :uuid (random-uuid))
|
|
|
+ ;; Stringified version of frontend.colors/COLORS. Too basic to couple
|
|
|
+ ["tomato" "red" "crimson" "pink" "plum" "purple" "violet" "indigo" "blue" "cyan" "teal" "green" "grass" "orange" "brown"])}
|
|
|
;; table-v2 props
|
|
|
- :logseq.table.version {:schema {:type :number :hide? true}
|
|
|
- :visible true}
|
|
|
- :logseq.table.compact {:schema {:type :checkbox :hide? true}
|
|
|
- :visible true}
|
|
|
- :logseq.table.headers {:schema
|
|
|
- {:type :default :hide? true}
|
|
|
- :closed-values
|
|
|
- (mapv #(hash-map :db-ident (keyword "logseq.property.table" (str "headers." %))
|
|
|
- :value %
|
|
|
- :uuid (random-uuid))
|
|
|
- ["uppercase" "capitalize" "capitalize-first" "lowercase"])
|
|
|
- :visible true}
|
|
|
- :logseq.table.hover {:schema
|
|
|
- {:type :default :hide? true}
|
|
|
- :closed-values
|
|
|
- (mapv #(hash-map :db-ident (keyword "logseq.property.table" (str "hover." %))
|
|
|
- :value %
|
|
|
- :uuid (random-uuid))
|
|
|
- ["row" "col" "both" "none"])
|
|
|
- :visible true}
|
|
|
- :logseq.table.borders {:schema {:type :checkbox :hide? true}
|
|
|
- :visible true}
|
|
|
- :logseq.table.stripes {:schema {:type :checkbox :hide? true}
|
|
|
- :visible true}
|
|
|
- :logseq.table.max-width {:schema {:type :number :hide? true}
|
|
|
- :visible true}
|
|
|
+ :logseq.property.table/version {:name :logseq.table.version
|
|
|
+ :schema {:type :number :hide? true :public? true :view-context :block}}
|
|
|
+ :logseq.property.table/compact {:name :logseq.table.compact
|
|
|
+ :schema {:type :checkbox :hide? true :public? true :view-context :block}}
|
|
|
+ :logseq.property.table/headers
|
|
|
+ {:name :logseq.table.headers
|
|
|
+ :schema
|
|
|
+ {:type :default :hide? true :public? true :view-context :block}
|
|
|
+ :closed-values
|
|
|
+ (mapv #(hash-map :db-ident (keyword "logseq.property.table" (str "headers." %))
|
|
|
+ :value %
|
|
|
+ :uuid (random-uuid))
|
|
|
+ ["uppercase" "capitalize" "capitalize-first" "lowercase"])}
|
|
|
+ :logseq.property.table/hover
|
|
|
+ {:name :logseq.table.hover
|
|
|
+ :schema
|
|
|
+ {:type :default :hide? true :public? true :view-context :block}
|
|
|
+ :closed-values
|
|
|
+ (mapv #(hash-map :db-ident (keyword "logseq.property.table" (str "hover." %))
|
|
|
+ :value %
|
|
|
+ :uuid (random-uuid))
|
|
|
+ ["row" "col" "both" "none"])}
|
|
|
+ :logseq.property.table/borders {:name :logseq.table.borders
|
|
|
+ :schema {:type :checkbox :hide? true :public? true :view-context :block}}
|
|
|
+ :logseq.property.table/stripes {:name :logseq.table.stripes
|
|
|
+ :schema {:type :checkbox :hide? true :public? true :view-context :block}}
|
|
|
+ :logseq.property.table/max-width {:name :logseq.table.max-width
|
|
|
+ :schema {:type :number :hide? true :public? true :view-context :block}}
|
|
|
|
|
|
- :icon {:original-name "Icon"
|
|
|
- :schema {:type :map}}
|
|
|
- :public {:schema {:type :checkbox :hide? true}
|
|
|
- :visible true}
|
|
|
- :filters {:schema {:type :map}}
|
|
|
- :exclude-from-graph-view {:schema {:type :checkbox :hide? true}
|
|
|
- :visible true}})
|
|
|
+ :logseq.property/icon {:original-name "Icon"
|
|
|
+ :schema {:type :map}}
|
|
|
+ :logseq.property/public {:schema
|
|
|
+ {:type :checkbox
|
|
|
+ :hide? true
|
|
|
+ :view-context :page
|
|
|
+ :public? true}}
|
|
|
+ :logseq.property/filters {:schema {:type :map}}
|
|
|
+ :logseq.property/exclude-from-graph-view {:schema
|
|
|
+ {:type :checkbox
|
|
|
+ :hide? true
|
|
|
+ :view-context :page
|
|
|
+ :public? true}}})
|
|
|
|
|
|
-(def visible-built-in-properties
|
|
|
- "These are built-in properties that users can see and use"
|
|
|
- (set (keep (fn [[k v]] (when (:visible v) k)) built-in-properties)))
|
|
|
-
|
|
|
-(defonce built-in-properties-keys
|
|
|
- (set (keys built-in-properties)))
|
|
|
-
|
|
|
-(def hidden-built-in-properties
|
|
|
- (set/difference built-in-properties-keys visible-built-in-properties))
|
|
|
-
|
|
|
-(defonce built-in-properties-keys-str
|
|
|
- (set (map name (keys built-in-properties))))
|
|
|
+(def built-in-properties
|
|
|
+ (->> built-in-properties*
|
|
|
+ (map (fn [[k v]]
|
|
|
+ (assert (and (keyword? k) (namespace k)))
|
|
|
+ [k
|
|
|
+ ;; All built-ins must have a :name
|
|
|
+ (if (:name v)
|
|
|
+ v
|
|
|
+ (assoc v :name (keyword (string/lower-case (name k)))))]))
|
|
|
+ (into {})))
|
|
|
|
|
|
(defn valid-property-name?
|
|
|
[s]
|
|
|
@@ -171,25 +190,27 @@
|
|
|
;; Disallow tags or page refs as they would create unreferenceable page names
|
|
|
(not (re-find #"^(#|\[\[)" s)))
|
|
|
|
|
|
+(defn get-pid
|
|
|
+ "Get a built-in property's id (keyword name for file graph and uuid for db graph)
|
|
|
+ given its db-ident. Use this fn on a file or db graph. Use
|
|
|
+ db-pu/get-built-in-property-uuid if only in a db graph context"
|
|
|
+ [repo db db-ident]
|
|
|
+ (if (sqlite-util/db-based-graph? repo)
|
|
|
+ db-ident
|
|
|
+ (get-in built-in-properties [db-ident :name])))
|
|
|
+
|
|
|
(defn lookup
|
|
|
- "Get the value of coll's (a map) `key`. For file and db graphs"
|
|
|
- [repo db coll key]
|
|
|
- (when db
|
|
|
- (let [property-name (if (keyword? key)
|
|
|
- (name key)
|
|
|
- key)]
|
|
|
- (if (sqlite-util/db-based-graph? repo)
|
|
|
- (when-let [property (d/entity db [:block/name (common-util/page-name-sanity-lc property-name)])]
|
|
|
- (get coll (:db/ident property)))
|
|
|
- (get coll key)))))
|
|
|
+ "Get the value of coll by db-ident. For file and db graphs"
|
|
|
+ [repo db coll db-ident]
|
|
|
+ (get coll (get-pid repo db db-ident)))
|
|
|
|
|
|
(defn get-block-property-value
|
|
|
- "Get the value of block's property `key`"
|
|
|
- [repo db block key]
|
|
|
+ "Get the value of built-in block's property by its db-ident"
|
|
|
+ [repo db block db-ident]
|
|
|
(when db
|
|
|
(let [block (or (d/entity db (:db/id block)) block)]
|
|
|
(when-let [properties (:block/properties block)]
|
|
|
- (lookup repo db properties key)))))
|
|
|
+ (lookup repo db properties db-ident)))))
|
|
|
|
|
|
(defn name->db-ident
|
|
|
"Converts a built-in property's keyword name to its :db/ident equivalent.
|
|
|
@@ -201,19 +222,12 @@
|
|
|
(keyword (str "logseq.property" additional-ns) prop-name)
|
|
|
(keyword "logseq.property" (name legacy-name))))
|
|
|
|
|
|
-(defn get-pid
|
|
|
- "Get a property's id (name or uuid) given its name. For file and db graphs"
|
|
|
- [repo db property-name]
|
|
|
- (if (sqlite-util/db-based-graph? repo)
|
|
|
- (:block/uuid (d/entity db [:block/name (common-util/page-name-sanity-lc (name property-name))]))
|
|
|
- property-name))
|
|
|
-
|
|
|
(defn shape-block?
|
|
|
[repo db block]
|
|
|
- (= :whiteboard-shape (get-block-property-value repo db block :ls-type)))
|
|
|
+ (= :whiteboard-shape (get-block-property-value repo db block :logseq.property/ls-type)))
|
|
|
|
|
|
(defn get-by-ident-or-name
|
|
|
- "Gets a property by ident or name"
|
|
|
+ "Gets a property by db-ident or name if it's a user property"
|
|
|
[db ident-or-name]
|
|
|
(if (and (keyword? ident-or-name) (namespace ident-or-name))
|
|
|
(d/entity db ident-or-name)
|