mirror of
https://github.com/logseq/logseq.git
synced 2026-05-23 20:24:15 +00:00
enhance(ux): bottom properties
This commit is contained in:
54
deps/outliner/src/logseq/outliner/property.cljs
vendored
54
deps/outliner/src/logseq/outliner/property.cljs
vendored
@@ -767,23 +767,37 @@
|
||||
(= "logseq.property.class" property-ns)
|
||||
(contains? db-property/schema-properties property-id))))
|
||||
|
||||
(defn- tag-class-page?
|
||||
[db block]
|
||||
(or (= :logseq.class/Tag (:db/ident block))
|
||||
(and db
|
||||
(ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Tag) block))))
|
||||
|
||||
(defn- bottom-position-property?
|
||||
[property]
|
||||
[db block property]
|
||||
(let [property-id (:db/ident property)
|
||||
property-type (:logseq.property/type property)]
|
||||
(and (not= :url property-type)
|
||||
(or (not= :default property-type)
|
||||
(seq (:property/closed-values property)))
|
||||
(not (schema-or-tag-related-property? property-id)))))
|
||||
(if (tag-class-page? db block)
|
||||
(or (contains? #{:logseq.property.class/extends
|
||||
:logseq.property.class/enable-bidirectional?}
|
||||
property-id)
|
||||
(and (not= :url property-type)
|
||||
(or (not= :default property-type)
|
||||
(seq (:property/closed-values property)))
|
||||
(not (schema-or-tag-related-property? property-id))))
|
||||
(and (not= :url property-type)
|
||||
(or (not= :default property-type)
|
||||
(seq (:property/closed-values property)))
|
||||
(not (schema-or-tag-related-property? property-id))))))
|
||||
|
||||
(defn- resolved-property-position
|
||||
[property]
|
||||
[db block property]
|
||||
(let [ui-position (:logseq.property/ui-position property)]
|
||||
(cond
|
||||
(contains? #{:block-left :block-right :block-below} ui-position)
|
||||
ui-position
|
||||
|
||||
(bottom-position-property? property)
|
||||
(bottom-position-property? db block property)
|
||||
:block-below
|
||||
|
||||
:else
|
||||
@@ -792,7 +806,7 @@
|
||||
(defn- property-with-position?
|
||||
[db property-id block position]
|
||||
(when-let [property (entity-plus/entity-memoized db property-id)]
|
||||
(let [property-position' (resolved-property-position property)]
|
||||
(let [property-position' (resolved-property-position db block property)]
|
||||
(and
|
||||
(= property-position' position)
|
||||
(not (and (:logseq.property/hide-empty-value property)
|
||||
@@ -800,20 +814,28 @@
|
||||
(not (:logseq.property/hide? property))
|
||||
(not (and
|
||||
(= property-position' :block-below)
|
||||
(nil? (get block property-id))))))))
|
||||
(nil? (get block property-id))
|
||||
(not (tag-class-page? db block))))))))
|
||||
|
||||
(defn property-with-other-position?
|
||||
[property]
|
||||
(not= :properties (resolved-property-position property)))
|
||||
[db block property]
|
||||
(not= :properties (resolved-property-position db block property)))
|
||||
|
||||
(defn get-block-positioned-properties
|
||||
[db eid position]
|
||||
(let [block (d/entity db eid)
|
||||
own-properties (:block.temp/property-keys block)]
|
||||
(->> (:classes-properties (get-block-classes-properties db eid))
|
||||
(map :db/ident)
|
||||
(concat own-properties)
|
||||
(distinct)
|
||||
own-properties (:block.temp/property-keys block)
|
||||
tag-page-pill-properties (when (tag-class-page? db block)
|
||||
[:logseq.property.class/extends
|
||||
:logseq.property.class/enable-bidirectional?])
|
||||
positioned-property-ids (if (tag-class-page? db block)
|
||||
(->> (concat own-properties tag-page-pill-properties)
|
||||
distinct)
|
||||
(->> (:classes-properties (get-block-classes-properties db eid))
|
||||
(map :db/ident)
|
||||
(concat own-properties)
|
||||
distinct))]
|
||||
(->> positioned-property-ids
|
||||
(filter (fn [id] (property-with-position? db id block position)))
|
||||
(map #(d/entity db %))
|
||||
(ldb/sort-by-order))))
|
||||
|
||||
@@ -350,6 +350,8 @@
|
||||
(testing "explicit non-properties ui-position remains other-position"
|
||||
(is (true?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :number
|
||||
:logseq.property/ui-position :block-left}))))
|
||||
@@ -357,12 +359,16 @@
|
||||
(testing "number property without explicit ui-position defaults to bottom position"
|
||||
(is (true?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :number}))))
|
||||
|
||||
(testing "default property without closed values stays in normal property rows"
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :default
|
||||
:property/closed-values []}))))
|
||||
@@ -370,6 +376,8 @@
|
||||
(testing "default property with closed values is positioned in bottom row"
|
||||
(is (true?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :default
|
||||
:property/closed-values [{:db/id 1}]}))))
|
||||
@@ -377,12 +385,16 @@
|
||||
(testing "url property without explicit ui-position stays in normal property rows"
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :url}))))
|
||||
|
||||
(testing "explicit left/right ui-position remains in positioned rows"
|
||||
(is (true?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :user.property/p1
|
||||
:logseq.property/type :url
|
||||
:logseq.property/ui-position :block-left}))))
|
||||
@@ -390,20 +402,28 @@
|
||||
(testing "bidirectional config property stays in normal property rows"
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :logseq.property.class/enable-bidirectional?
|
||||
:logseq.property/type :checkbox}))))
|
||||
|
||||
(testing "tag properties and schema-related properties stay in normal property rows"
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :block/tags
|
||||
:logseq.property/type :class})))
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :logseq.property.class/properties
|
||||
:logseq.property/type :property})))
|
||||
(is (false?
|
||||
(outliner-property/property-with-other-position?
|
||||
nil
|
||||
{}
|
||||
{:db/ident :logseq.property/public?
|
||||
:logseq.property/type :checkbox}))))
|
||||
|
||||
|
||||
@@ -2585,49 +2585,64 @@
|
||||
(rum/defc block-positioned-properties
|
||||
[config block position]
|
||||
(let [properties (outliner-property/get-block-positioned-properties (db/get-db) (:db/id block) position)
|
||||
has-viewable-properties? (seq properties)
|
||||
opts (merge config
|
||||
{:icon? true
|
||||
:page-cp page-cp
|
||||
:block-cp blocks-container
|
||||
:inline-text inline-text
|
||||
:other-position? true
|
||||
:property-position position})]
|
||||
(when (seq properties)
|
||||
(case position
|
||||
:property-position position})
|
||||
show-page-hidden-properties-toggle? (and (ldb/page? block)
|
||||
(not config/publishing?))
|
||||
show-hidden-properties-toggle? (and has-viewable-properties?
|
||||
show-page-hidden-properties-toggle?
|
||||
(property-component/has-hidden-properties? block config))
|
||||
show-page-add-property? (and (ldb/page? block)
|
||||
(not (ldb/class? block))
|
||||
(not config/publishing?))
|
||||
show-add-property-button? (and has-viewable-properties?
|
||||
show-page-add-property?)]
|
||||
(case position
|
||||
:block-below
|
||||
[:div.positioned-properties.block-below.flex.flex-row.gap-2.items-center.flex-wrap.text-sm.overflow-x-hidden
|
||||
(for [[idx property] (map-indexed vector properties)]
|
||||
[:div.bottom-property-pair.flex.flex-row.items-center.gap-1
|
||||
{:key (str (:db/id block) "-" (:db/id property))}
|
||||
[:div.flex.flex-row.items-center
|
||||
(property-component/property-key-cp block property opts)
|
||||
[:div.select-none ":"]]
|
||||
[:div.bottom-property-content.ls-block.property-value-container
|
||||
{:style {:min-height 20}}
|
||||
(pv/property-value block property opts)
|
||||
(when (and (contains? #{:date :number} (:logseq.property/type property))
|
||||
(not config/publishing?))
|
||||
[:button.bottom-property-edit-icon.select-none
|
||||
{:type "button"
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(when-let [trigger
|
||||
(some-> (.-currentTarget e)
|
||||
(.closest ".bottom-property-content")
|
||||
(.querySelector ".jtrigger"))]
|
||||
(.click trigger)
|
||||
(some-> trigger .focus)))}
|
||||
(ui/icon "edit" {:size 14})])]
|
||||
(when (< idx (dec (count properties)))
|
||||
[:div.bottom-property-separator.select-none ","])])
|
||||
(when (and (ldb/page? block) (not config/publishing?))
|
||||
(property-component/hidden-properties-toggle-button block))]
|
||||
[:div.positioned-properties.flex.flex-row.gap-1.select-none.h-6.self-start
|
||||
{:class (name position)}
|
||||
(for [property properties]
|
||||
(rum/with-key
|
||||
(pv/property-value block property (assoc opts :show-tooltip? true))
|
||||
(str (:db/id block) "-" (:db/id property))))]))))
|
||||
(when has-viewable-properties?
|
||||
[:div.positioned-properties.block-below.flex.flex-col.gap-1.text-sm.overflow-x-hidden
|
||||
[:div.bottom-properties-row.flex.flex-row.gap-2.items-center.flex-wrap.overflow-x-hidden
|
||||
(for [property properties]
|
||||
[:div.bottom-property-pill
|
||||
{:key (str (:db/id block) "-" (:db/id property))}
|
||||
[:div.flex.flex-row.items-center
|
||||
(property-component/property-key-cp block property opts)
|
||||
[:div.select-none ":"]]
|
||||
[:div.bottom-property-content.ls-block.property-value-container
|
||||
{:style {:min-height 20}}
|
||||
(pv/property-value block property opts)
|
||||
(when (contains? #{:number :date :datetime} (:logseq.property/type property))
|
||||
[:button.bottom-property-edit-icon.select-none
|
||||
{:type "button"
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(when-let [trigger
|
||||
(some-> (.-currentTarget e)
|
||||
(.closest ".bottom-property-content")
|
||||
(.querySelector ".jtrigger"))]
|
||||
(.click trigger)
|
||||
(some-> trigger .focus)))}
|
||||
(ui/icon "edit" {:size 14})])]])
|
||||
(when show-hidden-properties-toggle?
|
||||
(property-component/hidden-properties-toggle-button block {:icon-only? true}))
|
||||
(when show-add-property-button?
|
||||
(property-component/new-property block (assoc opts
|
||||
:property-position :block-below
|
||||
:icon-only? true)))]])
|
||||
|
||||
(when (seq properties)
|
||||
[:div.positioned-properties.flex.flex-row.gap-1.select-none.h-6.self-start
|
||||
{:class (name position)}
|
||||
(for [property properties]
|
||||
(rum/with-key
|
||||
(pv/property-value block property (assoc opts :show-tooltip? true))
|
||||
(str (:db/id block) "-" (:db/id property))))]))))
|
||||
|
||||
(rum/defc block-reactions < rum/reactive db-mixins/query
|
||||
[block]
|
||||
|
||||
@@ -1059,29 +1059,58 @@ html.is-mac {
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
.property-k {
|
||||
color: var(--ls-primary-text-color);
|
||||
.bottom-properties-row {
|
||||
@apply gap-1.5 py-1;
|
||||
}
|
||||
|
||||
.bottom-property-pair {
|
||||
@apply min-w-0;
|
||||
.bottom-property-pill {
|
||||
@apply inline-flex items-center gap-1 rounded-full px-2 py-0.5 h-6 max-w-full;
|
||||
background-color: var(--ls-secondary-background-color);
|
||||
box-shadow: inset 0 0 0 1px var(--ls-border-color);
|
||||
}
|
||||
|
||||
.bottom-property-content {
|
||||
@apply flex items-center gap-1 min-w-0;
|
||||
}
|
||||
|
||||
.bottom-property-separator {
|
||||
@apply text-muted-foreground;
|
||||
.bottom-property-pill .property-key-inner {
|
||||
@apply flex items-center min-w-0;
|
||||
}
|
||||
|
||||
.bottom-property-pill .property-k {
|
||||
@apply w-auto flex-none whitespace-nowrap leading-[20px];
|
||||
max-width: none;
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.bottom-property-pill a.property-k,
|
||||
.bottom-property-pill .property-key-inner a {
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.ls-new-property {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
.ls-new-property .jtrigger,
|
||||
.bottom-property-control-btn {
|
||||
@apply inline-flex items-center rounded-full px-1.5 py-0.5 h-6 text-xs text-muted-foreground;
|
||||
box-shadow: inset 0 0 0 1px var(--ls-border-color);
|
||||
}
|
||||
|
||||
.bottom-property-edit-icon {
|
||||
@apply text-muted-foreground inline-flex items-center justify-center p-0 bg-transparent border-0 cursor-pointer;
|
||||
@apply inline-flex items-center justify-center p-0 bg-transparent border-0 text-muted-foreground cursor-pointer;
|
||||
}
|
||||
|
||||
.ls-block.property-value-container {
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
flex: 0 1 auto;
|
||||
width: auto;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.property-value {
|
||||
@apply pl-0 min-h-[20px];
|
||||
}
|
||||
|
||||
.property-value-inner,
|
||||
@@ -1096,6 +1125,11 @@ html.is-mac {
|
||||
@apply self-center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.bullet-container,
|
||||
.property-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.block-tags {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[frontend.components.dnd :as dnd]
|
||||
[frontend.components.icon :as icon-component]
|
||||
[frontend.components.property.config :as property-config]
|
||||
[frontend.components.property.value :as pv]
|
||||
[frontend.components.select :as select]
|
||||
@@ -307,48 +306,13 @@
|
||||
(db-property/built-in-display-title property t)))
|
||||
|
||||
(rum/defc property-key-cp < rum/static
|
||||
[block property {:keys [other-position? class-schema?]}]
|
||||
(let [icon (:logseq.property/icon property)]
|
||||
[:div.property-key-inner.jtrigger-view
|
||||
;; icon picker
|
||||
(when-not other-position?
|
||||
(let [content-fn (fn [{:keys [id]}]
|
||||
(icon-component/icon-search
|
||||
{:on-chosen
|
||||
(fn [_e icon]
|
||||
(if icon
|
||||
(db-property-handler/set-block-property! (:db/id property)
|
||||
:logseq.property/icon icon)
|
||||
(db-property-handler/remove-block-property! (:db/id property)
|
||||
:logseq.property/icon))
|
||||
(shui/popup-hide! id))
|
||||
:icon-value icon
|
||||
:del-btn? (boolean icon)}))]
|
||||
|
||||
[:div.property-icon
|
||||
(shui/trigger-as
|
||||
:button.property-m
|
||||
(-> (when-not config/publishing?
|
||||
{:on-click (fn [^js e]
|
||||
(shui/popup-show! (.-target e) content-fn
|
||||
{:as-dropdown? true :auto-focus? true
|
||||
:content-props {:onEscapeKeyDown #(.preventDefault %)}}))})
|
||||
(assoc :class "flex items-center"))
|
||||
(if icon
|
||||
(icon-component/icon icon {:size 15 :color? true})
|
||||
(property-icon property nil)))]))
|
||||
|
||||
(if config/publishing?
|
||||
[:a.property-k.flex.select-none.jtrigger
|
||||
{:on-click #(route-handler/redirect-to-page! (:block/uuid property))}
|
||||
(db-property/built-in-display-title property t)]
|
||||
(property-key-title block property class-schema?))]))
|
||||
|
||||
(defn- bidirectional-property-icon-cp
|
||||
[property]
|
||||
(if-let [icon (:logseq.property/icon property)]
|
||||
(icon-component/icon icon {:size 15 :color? true})
|
||||
(ui/icon "letter-b" {:class "opacity-50" :size 15})))
|
||||
[block property {:keys [class-schema?]}]
|
||||
[:div.property-key-inner.jtrigger-view
|
||||
(if config/publishing?
|
||||
[:a.property-k.flex.select-none.jtrigger
|
||||
{:on-click #(route-handler/redirect-to-page! (:block/uuid property))}
|
||||
(db-property/built-in-display-title property t)]
|
||||
(property-key-title block property class-schema?))])
|
||||
|
||||
(rum/defcs bidirectional-values-cp < rum/static
|
||||
{:init (fn [state]
|
||||
@@ -370,11 +334,9 @@
|
||||
[bidirectional-properties]
|
||||
(when (seq bidirectional-properties)
|
||||
(for [{:keys [class title entities]} bidirectional-properties]
|
||||
[:div.property-pair.items-start {:key (str "bidirectional-" title)}
|
||||
[:div.property-key
|
||||
[:div.property-pair.property-panel-row {:key (str "bidirectional-" title)}
|
||||
[:div.property-key-panel
|
||||
[:div.property-key-inner
|
||||
[:div.property-icon
|
||||
(bidirectional-property-icon-cp class)]
|
||||
(if class
|
||||
[:a.property-k.flex.select-none.w-full.jtrigger
|
||||
{:on-click (fn [e]
|
||||
@@ -382,8 +344,11 @@
|
||||
(route-handler/redirect-to-page! (:block/uuid class)))}
|
||||
title]
|
||||
[:div.property-k.flex.select-none.w-full title])]]
|
||||
[:div.ls-block.property-value-container.flex.flex-row.gap-1.items-start
|
||||
[:div.property-value.flex.flex-1
|
||||
[:div.ls-block.property-value-container.property-value-panel
|
||||
[:div.property-panel-bullet {:aria-hidden true}
|
||||
[:span.bullet-container
|
||||
[:span.bullet]]]
|
||||
[:div.property-value.property-value-panel-inner.flex.flex-1
|
||||
(bidirectional-values-cp entities)]]])))
|
||||
|
||||
(rum/defcs ^:large-vars/cleanup-todo property-input < rum/reactive
|
||||
@@ -481,21 +446,25 @@
|
||||
(rum/defcs new-property < rum/reactive
|
||||
[state block opts]
|
||||
(when-not config/publishing?
|
||||
(let [add-new-property! (fn [e]
|
||||
(let [icon-only? (:icon-only? opts)
|
||||
add-new-property! (fn [e]
|
||||
(state/pub-event! [:editor/new-property (merge opts {:block block
|
||||
:target (.-target e)})]))]
|
||||
[:div.ls-new-property {:style {:margin-left 7 :margin-top 1 :font-size 15}}
|
||||
[:a.flex.jtrigger
|
||||
{:tab-index 0
|
||||
:on-click add-new-property!
|
||||
:on-key-press (fn [e]
|
||||
(when (contains? #{"Enter" " "} (util/ekey e))
|
||||
(.preventDefault e)
|
||||
(add-new-property! e)))}
|
||||
[:div.flex.flex-row.items-center.shrink-0
|
||||
(ui/icon "plus" {:size 15 :class "opacity-50"})
|
||||
[:div.ml-1 {:style {:margin-top 1}}
|
||||
(t :property/add-new)]]]])))
|
||||
[:div.ls-new-property
|
||||
(shui/button
|
||||
{:variant :outline
|
||||
:size :small
|
||||
:class "jtrigger flex !px-2 !py-1"
|
||||
:tab-index 0
|
||||
:title (t :property/add-new)
|
||||
:on-click add-new-property!
|
||||
:on-key-press (fn [e]
|
||||
(when (contains? #{"Enter" " "} (util/ekey e))
|
||||
(.preventDefault e)
|
||||
(add-new-property! e)))}
|
||||
(ui/icon "plus")
|
||||
(when-not icon-only?
|
||||
(t :property/add-new)))])))
|
||||
|
||||
(defn- resolve-linked-block-if-exists
|
||||
"Properties will be updated for the linked page instead of the refed block.
|
||||
@@ -511,59 +480,64 @@
|
||||
(db/sub-block (:db/id linked-block))
|
||||
(db/sub-block (:db/id block))))
|
||||
|
||||
(defn- empty-panel-property-value?
|
||||
[value]
|
||||
(or (nil? value)
|
||||
(and (map? value)
|
||||
(= :logseq.property/empty-placeholder (:db/ident value)))
|
||||
(and (coll? value)
|
||||
(or (empty? value)
|
||||
(every? (fn [item]
|
||||
(and (map? item)
|
||||
(= :logseq.property/empty-placeholder (:db/ident item))))
|
||||
value)))))
|
||||
|
||||
(rum/defc property-cp <
|
||||
rum/reactive
|
||||
db-mixins/query
|
||||
[block k v {:keys [inline-text page-cp sortable-opts] :as opts}]
|
||||
[block k v {:keys [sortable-opts] :as opts}]
|
||||
(when (keyword? k)
|
||||
(when-let [property (db/sub-block (:db/id (db/entity k)))]
|
||||
(let [type (get property :logseq.property/type :default)
|
||||
closed-values? (seq (:property/closed-values property))
|
||||
block? (and v
|
||||
(not closed-values?)
|
||||
(or (and (map? v) (:block/page v))
|
||||
(and (coll? v)
|
||||
(map? (first v))
|
||||
(or (:block/page (first v))
|
||||
(= :logseq.property/empty-placeholder (:db/ident (first v))))))
|
||||
(contains? #{:default :url} type))
|
||||
date? (= type :date)
|
||||
datetime? (= type :datetime)
|
||||
checkbox? (= type :checkbox)
|
||||
number-type? (= type :number)
|
||||
property-key-cp' (property-key-cp block property (assoc (select-keys opts [:class-schema?])
|
||||
:block? block?
|
||||
:inline-text inline-text
|
||||
:page-cp page-cp))]
|
||||
default-or-url? (contains? #{:default :url} type)
|
||||
empty-value? (empty-panel-property-value? v)
|
||||
show-panel-bullet? (or (not default-or-url?)
|
||||
empty-value?)
|
||||
property-key-cp' (property-key-cp block property (select-keys opts [:class-schema?]))]
|
||||
[:div {:key (str "property-pair-" (:db/id block) "-" (:db/id property))
|
||||
:class (cond
|
||||
(or date? datetime? checkbox? number-type?)
|
||||
"property-pair items-center"
|
||||
:else
|
||||
"property-pair items-start")
|
||||
:class (util/classnames ["property-pair property-panel-row"
|
||||
{:property-panel-row-empty empty-value?}])
|
||||
:data-property-title (:block/title property)
|
||||
:data-property-type (name type)}
|
||||
(if (seq sortable-opts)
|
||||
(dnd/sortable-item (assoc sortable-opts :class "property-key") property-key-cp')
|
||||
[:div.property-key property-key-cp'])
|
||||
(dnd/sortable-item
|
||||
(assoc sortable-opts :class "property-key-panel")
|
||||
property-key-cp')
|
||||
[:div.property-key-panel
|
||||
property-key-cp'])
|
||||
|
||||
(let [property-desc (when-not (= (:db/ident property) :logseq.property/description)
|
||||
(:logseq.property/description property))
|
||||
block' (assoc block (:db/ident property) v)]
|
||||
[:div.ls-block.property-value-container.flex.flex-row.gap-1
|
||||
{:class (if (contains? #{:checkbox :date :datetime} type)
|
||||
"items-center"
|
||||
"items-start")}
|
||||
|
||||
(when-not (or block? (and property-desc (:class-schema? opts)))
|
||||
[:div.flex.items-center {:style {:height 28}}
|
||||
[:div {:class "pl-1.5 -mr-[3px] opacity-60"}
|
||||
[:span.bullet-container [:span.bullet]]]])
|
||||
[:div.flex.flex-1
|
||||
[:div.property-value.flex.flex-1
|
||||
(if (:class-schema? opts)
|
||||
(pv/property-value property (db/entity :logseq.property/description) opts)
|
||||
(pv/property-value block' property opts))]]])]))))
|
||||
(let [block' (assoc block (:db/ident property) v)]
|
||||
[:div.ls-block.property-value-container.property-value-panel
|
||||
(when show-panel-bullet?
|
||||
[:div.property-panel-bullet {:aria-hidden true}
|
||||
[:span.bullet-container
|
||||
[:span.bullet]]])
|
||||
[:div.property-value.property-value-panel-inner.flex.flex-1
|
||||
(if (:class-schema? opts)
|
||||
(pv/property-value property (db/entity :logseq.property/description) opts)
|
||||
(pv/property-value block' property (assoc opts :suppress-inline-edit-icon? true)))]
|
||||
(when (contains? #{:number :date :datetime} type)
|
||||
[:button.property-panel-edit-btn.select-none
|
||||
{:type "button"
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(when-let [trigger
|
||||
(some-> (.-currentTarget e)
|
||||
(.closest ".property-value-panel")
|
||||
(.querySelector ".jtrigger"))]
|
||||
(.click trigger)
|
||||
(some-> trigger .focus)))}
|
||||
(ui/icon "edit" {:size 14})])])]))))
|
||||
|
||||
(defn- entity-ref-value?
|
||||
[value]
|
||||
@@ -696,24 +670,120 @@
|
||||
[block-uuid]
|
||||
(contains? @*show-hidden-properties-block-ids block-uuid))
|
||||
|
||||
(defn has-hidden-properties?
|
||||
[block {:keys [gallery-view?]}]
|
||||
(let [current-db (db/get-db)
|
||||
show-empty-and-hidden-state (some-> @state/state
|
||||
(get :ui/show-empty-and-hidden-properties?)
|
||||
deref)
|
||||
{:keys [mode show? ids]} show-empty-and-hidden-state
|
||||
show-empty-and-hidden-properties? (and show?
|
||||
(or (= mode :global)
|
||||
(and (set? ids) (contains? ids (:block/uuid block)))))
|
||||
properties* (cond-> (:block/properties block)
|
||||
(and (ldb/class? block)
|
||||
(not (ldb/built-in? block)))
|
||||
(assoc :logseq.property.class/enable-bidirectional?
|
||||
(:logseq.property.class/enable-bidirectional? block)))
|
||||
{:keys [properties recycled-only-property-ids]}
|
||||
(sanitize-property-values-for-display properties*)
|
||||
remove-built-in-or-other-position-properties
|
||||
(fn [property-pairs show-in-hidden-properties?]
|
||||
(remove (fn [property]
|
||||
(let [id (if (vector? property) (first property) property)]
|
||||
(or
|
||||
(= id :block/tags)
|
||||
(when-let [ent (db/entity id)]
|
||||
(or
|
||||
;; built-in
|
||||
(and (not (ldb/public-built-in-property? ent))
|
||||
(ldb/built-in? ent))
|
||||
;; other position
|
||||
(when-not (or
|
||||
show-empty-and-hidden-properties?
|
||||
show-in-hidden-properties?)
|
||||
(outliner-property/property-with-other-position? current-db block ent))
|
||||
(and gallery-view?
|
||||
(contains? #{:logseq.property.class/properties} (:db/ident ent))))))))
|
||||
property-pairs))
|
||||
{:keys [all-classes classes-properties]} (outliner-property/get-block-classes-properties current-db (:db/id block))
|
||||
classes-properties-set (set (map :db/ident classes-properties))
|
||||
block-own-properties (->> properties
|
||||
(remove (fn [[id _]] (contains? recycled-only-property-ids id)))
|
||||
(remove (fn [[id _]] (classes-properties-set id))))
|
||||
state-hide-empty-properties? (:ui/hide-empty-properties? (state/get-config))
|
||||
hide-with-property-id (fn [property-id]
|
||||
(let [property (db/entity property-id)]
|
||||
(boolean
|
||||
(cond
|
||||
show-empty-and-hidden-properties?
|
||||
false
|
||||
state-hide-empty-properties?
|
||||
(nil? (get properties property-id))
|
||||
(and (:logseq.property/hide-empty-value property)
|
||||
(nil? (get properties property-id)))
|
||||
true
|
||||
:else
|
||||
(boolean (:logseq.property/hide? property))))))
|
||||
property-hide-f (cond
|
||||
config/publishing?
|
||||
;; Publishing is read only so hide all blank properties as they
|
||||
;; won't be edited and distract from properties that have values
|
||||
(fn [[property-id property-value]]
|
||||
(or (nil? property-value)
|
||||
(hide-with-property-id property-id)))
|
||||
state-hide-empty-properties?
|
||||
(fn [[property-id property-value]]
|
||||
;; User's selection takes precedence over config
|
||||
(if (:logseq.property/hide? (db/entity property-id))
|
||||
(hide-with-property-id property-id)
|
||||
(nil? property-value)))
|
||||
:else
|
||||
(comp hide-with-property-id first))
|
||||
{block-hidden-properties true
|
||||
block-own-properties' false} (group-by property-hide-f block-own-properties)
|
||||
class-properties (loop [classes all-classes
|
||||
existing-properties (set (map first block-own-properties'))
|
||||
result []]
|
||||
(if-let [class (first classes)]
|
||||
(let [cur-properties (->> (db-property/get-class-ordered-properties class)
|
||||
(map :db/ident)
|
||||
(remove existing-properties))]
|
||||
(recur (rest classes)
|
||||
(set/union existing-properties (set cur-properties))
|
||||
(if (seq cur-properties)
|
||||
(into result cur-properties)
|
||||
result)))
|
||||
result))
|
||||
class-property-pairs (->> class-properties
|
||||
(map (fn [p] [p (get properties p)]))
|
||||
(remove (fn [[property-id _]]
|
||||
(contains? recycled-only-property-ids property-id))))
|
||||
hidden-properties (-> (concat block-hidden-properties
|
||||
(filter property-hide-f class-property-pairs))
|
||||
(remove-built-in-or-other-position-properties true))]
|
||||
(boolean (seq hidden-properties))))
|
||||
|
||||
(rum/defc hidden-properties-toggle-button
|
||||
[block]
|
||||
[block {:keys [icon-only?] :as _opts}]
|
||||
(let [block-uuid (:block/uuid block)]
|
||||
(when block-uuid
|
||||
(shui/button
|
||||
{:variant :ghost
|
||||
:size :sm
|
||||
:class "px-1 text-muted-foreground h-6 text-xs"
|
||||
:class "bottom-property-control-btn px-1 text-muted-foreground h-6 text-xs"
|
||||
:title (t :property/hidden-properties)
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(toggle-hidden-properties-visibility! block-uuid))}
|
||||
(t :property/hidden-properties)))))
|
||||
(if icon-only?
|
||||
[:span.select-none "⋯"]
|
||||
(t :property/hidden-properties))))))
|
||||
|
||||
(rum/defc hidden-properties-cp
|
||||
[block hidden-properties {:keys [show-hidden-properties?] :as opts}]
|
||||
(when (and show-hidden-properties? (seq hidden-properties))
|
||||
[:div.mt-1
|
||||
(properties-section block hidden-properties opts)]))
|
||||
(properties-section block hidden-properties opts)))
|
||||
|
||||
(rum/defc load-bidirectional-properties < rum/static
|
||||
[block root-block? set-bidirectional-properties!]
|
||||
@@ -737,6 +807,7 @@
|
||||
(let [*bidirectional-properties (::bidirectional-properties state)
|
||||
bidirectional-properties @*bidirectional-properties
|
||||
id (::id state)
|
||||
current-db (db/get-db)
|
||||
db-id (:db/id (::block state))
|
||||
block (db/sub-block db-id)
|
||||
show-properties? (or sidebar-properties? tag-dialog?)
|
||||
@@ -768,7 +839,7 @@
|
||||
(when-not (or
|
||||
show-empty-and-hidden-properties?
|
||||
show-in-hidden-properties?)
|
||||
(outliner-property/property-with-other-position? ent))
|
||||
(outliner-property/property-with-other-position? current-db block ent))
|
||||
|
||||
(and (:gallery-view? opts)
|
||||
(contains? #{:logseq.property.class/properties} (:db/ident ent))))))))
|
||||
@@ -846,15 +917,12 @@
|
||||
(not has-bidirectional-properties?))
|
||||
nil
|
||||
|
||||
(and (empty? full-properties) (empty? hidden-properties) (not has-bidirectional-properties?))
|
||||
(when show-properties?
|
||||
(rum/with-key (new-property block opts) (str id "-add-property")))
|
||||
|
||||
:else
|
||||
(let [remove-properties #{:logseq.property/icon :logseq.property/query}
|
||||
properties' (->> (remove (fn [[k _v]] (contains? remove-properties k))
|
||||
full-properties)
|
||||
(remove (fn [[k _v]] (= k :logseq.property.class/properties))))
|
||||
show-properties-panel? (or (seq properties') (seq bidirectional-properties))
|
||||
page? (entity-util/page? block)
|
||||
class? (entity-util/class? block)]
|
||||
[:div.ls-properties-area
|
||||
@@ -862,16 +930,15 @@
|
||||
:class (util/classnames [{:ls-page-properties page?}])
|
||||
:tab-index 0}
|
||||
[:<>
|
||||
(properties-section block properties' opts)
|
||||
(bidirectional-properties-section bidirectional-properties)
|
||||
(when show-properties-panel?
|
||||
[:div.properties-panel
|
||||
(properties-section block properties' opts)
|
||||
(bidirectional-properties-section bidirectional-properties)])
|
||||
|
||||
(when-not class?
|
||||
(hidden-properties-cp block hidden-properties
|
||||
(assoc opts :show-hidden-properties? show-hidden-properties?)))
|
||||
|
||||
(when (and page? (not class?))
|
||||
(rum/with-key (new-property block opts) (str id "-add-property")))
|
||||
|
||||
(when class?
|
||||
(let [properties (->> (:logseq.property.class/properties block)
|
||||
(map (fn [e] [(:db/ident e)])))
|
||||
@@ -883,8 +950,9 @@
|
||||
(property-key-cp block (db/entity :logseq.property.class/properties) {})]]
|
||||
[:div.text-muted-foreground {:style {:margin-left 26}}
|
||||
(t :class/tag-properties-desc)]]
|
||||
[:div.ml-4
|
||||
[:div.ml-4.gap-1.flex.flex-col
|
||||
(properties-section block properties opts')
|
||||
(hidden-properties-cp block hidden-properties
|
||||
(assoc opts :show-hidden-properties? show-hidden-properties?))
|
||||
(rum/with-key (new-property block opts') (str id "-class-add-property"))]]))]])))]))
|
||||
[:div.ml-3
|
||||
(rum/with-key (new-property block opts') (str id "-class-add-property"))]]]))]])))]))
|
||||
|
||||
@@ -62,9 +62,70 @@
|
||||
}
|
||||
|
||||
.ls-properties-area {
|
||||
.property-pair {
|
||||
@apply flex flex-row flex-wrap space-x-1;
|
||||
.properties-panel {
|
||||
@apply rounded-md overflow-hidden;
|
||||
}
|
||||
|
||||
.properties-panel-header {
|
||||
@apply px-3 py-2 text-xs font-medium text-muted-foreground border-b border-gray-04;
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row {
|
||||
@apply grid items-start px-3 gap-3 w-full;
|
||||
grid-template-columns: fit-content(260px) minmax(0, 1fr);
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.property-key-panel {
|
||||
@apply flex items-center min-w-0;
|
||||
min-height: 28px;
|
||||
min-width: 150px;
|
||||
font-size: inherit;
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.property-key-panel .property-key-inner {
|
||||
@apply w-full min-w-0;
|
||||
}
|
||||
|
||||
.property-key-panel .property-k {
|
||||
@apply block min-w-0 max-w-full overflow-hidden text-ellipsis whitespace-nowrap;
|
||||
}
|
||||
|
||||
.property-value-panel.ls-block.property-value-container {
|
||||
@apply flex flex-row items-start gap-1 w-full;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row.property-panel-row-empty .property-value-panel.ls-block.property-value-container {
|
||||
@apply items-center;
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row[data-property-type=node] .property-value-panel.ls-block.property-value-container,
|
||||
.property-pair.property-panel-row[data-property-type=node] .property-value.property-value-panel-inner {
|
||||
@apply items-center;
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row .property-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.property-panel-bullet {
|
||||
@apply inline-flex items-center opacity-60;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.property-panel-bullet .bullet-container {
|
||||
@apply relative left-0 top-0;
|
||||
}
|
||||
|
||||
.property-panel-edit-btn {
|
||||
@apply inline-flex items-center justify-center p-0 bg-transparent border-0 text-muted-foreground cursor-pointer;
|
||||
}
|
||||
|
||||
.property-pair {
|
||||
.jtrigger-view {
|
||||
> .jtrigger-id {
|
||||
@apply left-[-11px];
|
||||
@@ -122,6 +183,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row .property-k {
|
||||
@apply leading-[20px];
|
||||
font-size: inherit;
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row a.property-k,
|
||||
.property-key-panel a {
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.property-value.property-value-panel-inner {
|
||||
@apply pl-0 min-h-[20px] w-full min-w-0;
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
|
||||
.property-value.property-value-panel-inner > .property-value-inner {
|
||||
@apply w-full min-w-0;
|
||||
}
|
||||
|
||||
.property-value.property-value-panel-inner .jtrigger {
|
||||
@apply w-full min-w-0;
|
||||
}
|
||||
|
||||
.property-value.property-value-panel-inner .select-item {
|
||||
@apply min-w-0;
|
||||
}
|
||||
|
||||
.property-pair.property-panel-row[data-property-type=node] .property-value.property-value-panel-inner .page-reference,
|
||||
.property-pair.property-panel-row[data-property-type=node] .property-value.property-value-panel-inner .page-ref {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.add-button-link {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -474,4 +568,4 @@ a.control-link {
|
||||
@apply py-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@
|
||||
:else nil)))
|
||||
|
||||
(rum/defc datetime-value
|
||||
[value property-id repeated-task?]
|
||||
[value property-id repeated-task? {:keys [datetime? other-position? suppress-inline-edit-icon?]}]
|
||||
(when-let [date (t/to-default-time-zone (tc/from-long value))]
|
||||
(let [content [:div.ls-datetime.flex.flex-row.gap-1.items-center
|
||||
(when-let [page-cp (state/get-component :block/page-cp)]
|
||||
@@ -442,15 +442,17 @@
|
||||
:label (human-date-label value)}
|
||||
{:block/name page-title})
|
||||
page-title)))
|
||||
(let [date (js/Date. value)
|
||||
hours (.getHours date)
|
||||
minutes (.getMinutes date)]
|
||||
[:span.select-none
|
||||
(if (= 0 hours minutes)
|
||||
(ui/icon "edit" {:size 14 :class "text-muted-foreground hover:text-foreground align-middle"})
|
||||
(str (util/zero-pad hours)
|
||||
":"
|
||||
(util/zero-pad minutes)))])]]
|
||||
(when datetime?
|
||||
(let [date (js/Date. value)
|
||||
hours (.getHours date)
|
||||
minutes (.getMinutes date)]
|
||||
[:span.select-none
|
||||
(if (= 0 hours minutes)
|
||||
(when-not (or other-position? suppress-inline-edit-icon?)
|
||||
(ui/icon "edit" {:size 14 :class "text-muted-foreground hover:text-foreground align-middle"}))
|
||||
(str (util/zero-pad hours)
|
||||
":"
|
||||
(util/zero-pad minutes)))]))]]
|
||||
(if (or repeated-task? (contains? #{:logseq.property/deadline :logseq.property/scheduled} property-id))
|
||||
(overdue date content)
|
||||
content))))
|
||||
@@ -462,7 +464,7 @@
|
||||
(:db/ident property)))
|
||||
|
||||
(rum/defc date-picker
|
||||
[value {:keys [block property datetime? on-change on-delete del-btn? editing? multiple-values? other-position?]}]
|
||||
[value {:keys [block property datetime? on-change on-delete del-btn? editing? multiple-values? other-position? suppress-inline-edit-icon?]}]
|
||||
(let [*el (hooks/use-ref nil)
|
||||
content-fn (fn [{:keys [id]}] (calendar-inner id
|
||||
{:block block
|
||||
@@ -526,7 +528,12 @@
|
||||
content))
|
||||
|
||||
(number? value)
|
||||
(datetime-value value (:db/ident property) repeated-task?)
|
||||
(datetime-value value
|
||||
(:db/ident property)
|
||||
repeated-task?
|
||||
{:datetime? datetime?
|
||||
:other-position? other-position?
|
||||
:suppress-inline-edit-icon? suppress-inline-edit-icon?})
|
||||
|
||||
:else
|
||||
(property-empty-btn-value nil))])))))
|
||||
|
||||
@@ -3039,7 +3039,7 @@
|
||||
(remove (fn [e] (attributes (:db/ident e))))
|
||||
(remove (fn [k]
|
||||
(when-not page-title?
|
||||
(outliner-property/property-with-other-position? k))))
|
||||
(outliner-property/property-with-other-position? db block k))))
|
||||
(remove (fn [e] (:logseq.property/hide? e)))
|
||||
(remove nil?))]
|
||||
(or (seq properties)
|
||||
|
||||
Reference in New Issue
Block a user