enhance(ux): set/unset #Page to convert between page and block (#11970)

* enhance(ux): add/remove #Page to toggle page/block

* enhance(ux): "p t" to set tags for selected blocks

Updated "p a" to toggle displaying all properties including hidden ones.

* feat: cmd+k move blocks

* fix: block could be converted to page and create invalid nodes

Pages with block namespace parents is nonsensical and pages in
property values led to multiple validation errors. Also guard
against class and property pages as namespace parents

* fix: warn on failed cut+paste or indent/outdent of page blocks. Better to warn than silent failure which leaves user confused on
what happened. 

---------

Co-authored-by: Gabriel Horner <gabriel@logseq.com>
This commit is contained in:
Tienson Qin
2025-08-05 17:45:57 +08:00
committed by GitHub
parent a2eee34d80
commit d84d3f9652
39 changed files with 950 additions and 435 deletions

View File

@@ -40,6 +40,10 @@
[promesa.core :as p]
[rum.core :as rum]))
(defn- get-action
[]
(:action (:search/args @state/state)))
(defn translate [t {:keys [id desc]}]
(when id
(let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
@@ -197,8 +201,10 @@
(first))))
(defn state->action [state]
(let [highlighted-item (state->highlighted-item state)]
(cond (:source-page highlighted-item) :open
(let [highlighted-item (state->highlighted-item state)
action (get-action)]
(cond (and (:source-page highlighted-item) (= action :move-blocks)) :trigger
(:source-page highlighted-item) :open
(:source-block highlighted-item) :open
(:file-path highlighted-item) :open
(:source-search highlighted-item) :search
@@ -327,7 +333,9 @@
repo (state/get-current-repo)
current-page (when-let [id (page-util/get-current-page-id)]
(db/entity id))
opts {:limit 100 :dev? config/dev? :built-in? true}]
opts (cond-> {:limit 100 :dev? config/dev? :built-in? true}
(contains? #{:move-blocks} (get-action))
(assoc :page-only? true))]
(swap! !results assoc-in [group :status] :loading)
(swap! !results assoc-in [:current-page :status] :loading)
(p/let [blocks (search/block-search repo @!input opts)
@@ -548,12 +556,21 @@
(reset! (::input state) search-query))))
(defmethod handle-action :trigger [_ state _event]
(let [command (some-> state state->highlighted-item :source-command)
dont-close-commands #{:graph/open :graph/remove :dev/replace-graph-with-db-file :misc/import-edn-data}]
(when-let [action (:action command)]
(let [highlighted-item (some-> state state->highlighted-item)
command (:source-command highlighted-item)
dont-close-commands #{:graph/open :graph/remove :dev/replace-graph-with-db-file :misc/import-edn-data :editor/move-blocks}
search-args (:search/args @state/state)
action (or (:action command)
(when-let [trigger (:trigger search-args)]
#(trigger highlighted-item)))
input-ref @(::input-ref state)]
(when action
(when input-ref
(set! (.-value input-ref) "")
(.focus input-ref))
(action)
(when-not (contains? dont-close-commands (:id command))
(shui/dialog-close! :ls-dialog-cmdk))
(util/schedule #(action) 32))))
(shui/dialog-close! :ls-dialog-cmdk)))))
(defmethod handle-action :create [_ state _event]
(let [item (state->highlighted-item state)
@@ -692,11 +709,13 @@
;; for some reason, the highlight effect does not always trigger on a
;; boolean value change so manually pass in the dep
:on-highlight-dep highlighted-item
:on-click (fn [e]
(reset! (::highlighted-item state) item)
(handle-action :default state item)
(when-let [on-click (:on-click item)]
(on-click e)))
:on-click
(fn [e]
(util/stop-propagation e)
(reset! (::highlighted-item state) item)
(handle-action :default state item)
(when-let [on-click (:on-click item)]
(on-click e)))
;; :on-mouse-enter (fn [e]
;; (when (not highlighted?)
;; (reset! (::highlighted-item state) (assoc item :mouse-enter-triggered-highlight true))))
@@ -805,12 +824,20 @@
(and enter? (not composing?)) (do
(handle-action :default state e)
(util/stop-propagation e))
esc? (let [filter' @(::filter state)]
(if filter'
esc? (let [filter' @(::filter state)
action (get-action)
move-blocks? (= :move-blocks action)]
(cond
(and move-blocks? (string/blank? input))
(state/close-modal!)
(and filter' (not move-blocks?))
(do
(util/stop e)
(reset! (::filter state) nil)
(load-results :default state))
:else
(when-not (string/blank? input)
(util/stop e)
(handle-input-change state nil ""))))
@@ -831,12 +858,15 @@
(defn- input-placeholder
[sidebar?]
(let [search-mode (:search/mode @state/state)
search-args (:search/args @state/state)]
action (get-action)]
(cond
(= action :move-blocks)
"Move blocks to"
(and (= search-mode :graph) (not sidebar?))
"Add graph filter"
(= search-args :new-page)
(= action :new-page)
"Type a page name to create"
:else
@@ -1000,8 +1030,10 @@
(shortcut/listen-all!))
state)}
{:init (fn [state]
(let [search-mode (:search/mode @state/state)
(let [search-mode (or (:search/mode @state/state) :global)
opts (last (:rum/args state))]
(when (nil? search-mode)
(state/set-state! :search/mode :global))
(assoc state
::ref (atom nil)
::filter (if (and search-mode
@@ -1032,8 +1064,10 @@
(rum/local false ::input-changed?)
[state {:keys [sidebar?] :as opts}]
(let [*input (::input state)
search-mode (:search/mode @state/state)
group-filter (:group (rum/react (::filter state)))
search-mode (state/sub :search/mode)
group-filter (or (when (and (not (contains? #{:global :graph} search-mode)) (not (:sidebar? opts)))
search-mode)
(:group (rum/react (::filter state))))
results-ordered (state->results-ordered state search-mode)
all-items (mapcat last results-ordered)
first-item (first all-items)]
@@ -1072,7 +1106,8 @@
(when-not sidebar? (hints state))]))
(rum/defc cmdk-modal [props]
[:div {:class "cp__cmdk__modal rounded-lg w-[90dvw] max-w-4xl relative"}
[:div {:class "cp__cmdk__modal rounded-lg w-[90dvw] max-w-4xl relative"
:data-keep-selection true}
(cmdk props)])
(rum/defc cmdk-block [props]

View File

@@ -1019,7 +1019,8 @@
:on-pointer-up (fn []
(when-let [container (gdom/getElement "app-container-wrapper")]
(d/remove-class! container "blocks-selection-mode")
(when (> (count (state/get-selection-blocks)) 1)
(when (and (> (count (state/get-selection-blocks)) 1)
(not (util/input? js/document.activeElement)))
(util/clear-selection!))))}
[:button#skip-to-main

View File

@@ -403,7 +403,8 @@
(when current-repo
(ui/with-shortcut :go/search "right"
[:button.button.icon#search-button
{:title (t :header/search)
{:data-keep-selection true
:title (t :header/search)
:on-click #(do (when (or (mobile-util/native-android?)
(mobile-util/native-iphone?))
(state/set-left-sidebar-open! false))

View File

@@ -7,7 +7,6 @@
[frontend.search :as search]
[frontend.state :as state]
[frontend.ui :as ui]
[logseq.db :as ldb]
[logseq.shui.hooks :as hooks]
[logseq.shui.ui :as shui]
[promesa.core :as p]
@@ -39,12 +38,8 @@
:selected-choices selected-choices
:on-chosen (fn [chosen selected?]
(if selected?
(let [last-child (->> (:block/_parent (db/entity (:db/id library-page)))
ldb/sort-by-order
last)
target (or last-child library-page)
chosen-block (db/entity chosen)]
(editor-handler/move-blocks! [chosen-block] target (if last-child true false))
(let [chosen-block (db/entity chosen)]
(editor-handler/move-blocks! [chosen-block] library-page {:bottom? true})
(set-selected-choices! (conj selected-choices chosen)))
(do
(db/transact! (state/get-current-repo)

View File

@@ -14,6 +14,7 @@
k (:property-key opts)]
(when-let [view-selected-blocks (:selected-blocks opts)]
(state/set-state! :view/selected-blocks view-selected-blocks))
(state/set-state! :ui/show-property-dialog? true)
(assoc state
::property-key (atom k)
::property (atom (when k (db/get-case-page k))))))
@@ -21,6 +22,7 @@
(when-let [close-fn (:on-dialog-close (last (:rum/args state)))]
(close-fn))
(state/set-state! :view/selected-blocks nil)
(state/set-state! :ui/show-property-dialog? false)
state)}
[state blocks opts]
(when (seq blocks)

View File

@@ -249,8 +249,7 @@
(p/do!
(ui-outliner-tx/transact!
{:outliner-op :save-block}
(doseq [block blocks]
(db-property-handler/delete-property-value! (:db/id block) (:db/ident property) value)))
(db-property-handler/batch-delete-property-value! (map :db/id blocks) (:db/ident property) value))
(when (or (not many?)
;; values will be cleared
(and many? (<= (count (get block (:db/ident property))) 1)))
@@ -698,10 +697,13 @@
excluded-options)
(contains? #{:class :property} property-type)
(let [classes (model/get-all-classes
repo
{:except-root-class? true
:except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))})]
(let [classes (cond->
(model/get-all-classes
repo
{:except-root-class? true
:except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))})
(not (or (and (entity-util/page? block) (not (ldb/internal-page? block))) (:logseq.property/created-from-property block)))
(conj (db/entity :logseq.class/Page)))]
(if (= property-type :class)
classes
(property-handler/get-class-property-choices)))
@@ -771,7 +773,8 @@
:label label
:value id
:disabled? (and tags? (contains?
(set/union #{:logseq.class/Journal :logseq.class/Whiteboard} ldb/internal-tags)
(set/union #{:logseq.class/Journal :logseq.class/Whiteboard}
(set/difference ldb/internal-tags #{:logseq.class/Page}))
(:db/ident node)))))) nodes)
classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
opts' (cond->
@@ -788,13 +791,12 @@
"Set alias"
:else
(str "Set " (:block/title property)))
:show-new-when-not-exact-match? (if (or extends-property?
;; Don't allow creating private tags
(and (= :block/tags (:db/ident property))
(seq (set/intersection (set (map :db/ident classes'))
ldb/private-tags))))
false
true)
:show-new-when-not-exact-match? (not
(or (and extends-property? (contains? (set children-pages) (:db/id block)))
;; Don't allow creating private tags
(and (= :block/tags (:db/ident property))
(seq (set/intersection (set (map :db/ident classes'))
ldb/private-tags)))))
:extract-chosen-fn :value
:extract-fn (fn [x] (or (:label-value x) (:label x)))
:input-opts input-opts
@@ -825,6 +827,10 @@
(when-not add-tag-property?
(log/error :msg "No :db/id found or created for chosen" :chosen chosen)))))})
(= :block/tags (:db/ident property))
(assoc :exact-match-exclude-items
(set (map (fn [ident] (:block/title (db/entity ident))) ldb/private-tags)))
(and (seq classes') (not tags-or-alias?))
(assoc
;; Provides additional completion for inline classes on new pages or objects

View File

@@ -172,6 +172,7 @@
(render-item result chosen? multiple-choices? *selected-choices)))
:class "cp__select-results"
:on-chosen (fn [raw-chosen e]
(util/stop-propagation e)
(when clear-input-on-chosen?
(reset! *input ""))
(let [chosen (extract-chosen-fn raw-chosen)]

View File

@@ -9,79 +9,81 @@
[logseq.shui.ui :as shui]
[rum.core :as rum]))
(rum/defc action-bar
(rum/defc action-bar < rum/reactive
[& {:keys [on-cut on-copy selected-blocks hide-dots? button-border?]
:or {on-cut #(editor-handler/cut-selection-blocks true)}}]
(let [selected-blocks (map (fn [block] (if (number? block) (db/entity block) block)) selected-blocks)
on-copy (if (and selected-blocks (nil? on-copy))
#(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks})
(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?
(when-not (or (state/sub :search/mode)
(state/sub :ui/show-property-dialog?))
(let [selected-blocks (map (fn [block] (if (number? block) (db/entity block) block)) selected-blocks)
on-copy (if (and selected-blocks (nil? on-copy))
#(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks})
(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)
(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/clear-selection!)
(state/pub-event! [:editor/hide-action-bar])))
"Copy")
(when db-graph?
(on-copy)
(state/clear-selection!)
(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)
(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))])
{:content-props {:class "w-[280px] ls-context-menu-content"}
:as-dropdown? true})))
(ui/icon "dots" {:size 13}))))]))
(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))])
{:content-props {:class "w-[280px] ls-context-menu-content"}
:as-dropdown? true})))
(ui/icon "dots" {:size 13}))))])))

View File

@@ -37,6 +37,12 @@
{:outliner-op :delete-property-value}
(outliner-op/delete-property-value! block-id property-id property-value)))
(defn batch-delete-property-value!
[block-ids property-id property-value]
(ui-outliner-tx/transact!
{:outliner-op :batch-delete-property-value}
(outliner-op/batch-delete-property-value! block-ids property-id property-value)))
(defn create-property-text-block!
[block-id property-id value opts]
(ui-outliner-tx/transact!

View File

@@ -59,12 +59,13 @@
(:block/uuid (ldb/get-left-sibling target-block)))]
(if first-child?
(when-let [parent (:block/parent target-block)]
(outliner-op/move-blocks! blocks' parent false))
(outliner-op/move-blocks! blocks' parent {:sibling? false}))
(if-let [before-node (ldb/get-left-sibling target-block)]
(outliner-op/move-blocks! blocks' before-node true)
(outliner-op/move-blocks! blocks' before-node {:sibling? true})
(when-let [parent (:block/parent target-block)]
(outliner-op/move-blocks! blocks' parent false)))))
(outliner-op/move-blocks! blocks' target-block (not nested?)))))
(outliner-op/move-blocks! blocks' parent {:sibling? false})))))
(outliner-op/move-blocks! blocks' target-block
{:sibling? (not nested?)}))))
:else
nil)))

View File

@@ -860,7 +860,7 @@
(ui-outliner-tx/transact!
transact-opts
(when (seq children)
(outliner-op/move-blocks! children prev-block false))
(outliner-op/move-blocks! children prev-block {:sibling? false}))
(delete-block-aux! block)
(save-block! repo prev-block new-content {})))))
@@ -870,11 +870,29 @@
(delete-block-aux! block)))))))))))
(defn move-blocks!
[blocks target sibling?]
[blocks target opts]
(when (seq blocks)
(ui-outliner-tx/transact!
{:outliner-op :move-blocks}
(outliner-op/move-blocks! blocks target sibling?))))
(outliner-op/move-blocks! blocks target opts))))
(defn move-selected-blocks
[e]
(util/stop e)
(let [block-ids (or (seq (state/get-selection-block-ids))
(when-let [id (:block/uuid (state/get-edit-block))]
[id]))]
(if (seq block-ids)
(let [blocks (->> (map (fn [id] (db/entity [:block/uuid id])) block-ids)
block-handler/get-top-level-blocks)]
(route-handler/go-to-search! :nodes
{:action :move-blocks
:blocks blocks
:trigger (fn [chosen]
(state/pub-event! [:editor/hide-action-bar])
(state/clear-selection!)
(move-blocks! blocks (:source-page chosen) {:bottom? true}))}))
(notification/show! "There's no block selected, please select blocks first." :warning))))
(defn delete-block!
[repo]
@@ -1284,6 +1302,10 @@
(some->> (shui-popup/get-popups)
(some #(some-> % (:id) (str) (string/includes? (str id))))))
(defn dialog-exists?
[id]
(shui-dialog/get-modal id))
(defn show-action-bar!
[& {:keys [delay]
:or {delay 200}}]
@@ -1644,7 +1666,12 @@
(defn get-matched-classes
"Return matched classes except the root tag"
[q]
(let [classes (->> (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
(let [editing-block (some-> (state/get-edit-block) :db/id db/entity)
non-page-block? (and editing-block (not (ldb/page? editing-block)))
all-classes (cond-> (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
non-page-block?
(conj (db/entity :logseq.class/Page)))
classes (->> all-classes
(mapcat (fn [class]
(conj (:block/alias class) class)))
(common-util/distinct-by :db/id)
@@ -3340,18 +3367,19 @@
(defn open-selected-block!
[direction e]
(let [selected-blocks (state/get-selection-blocks)
f (case direction :left first :right last)
node (some-> selected-blocks f)]
(if (some-> node (dom/has-class? "block-add-button"))
(.click node)
(when-let [block-id (some-> node (dom/attr "blockid") uuid)]
(util/stop e)
(let [block {:block/uuid block-id}
left? (= direction :left)
opts {:container-id (some-> node (dom/attr "containerid") (parse-long))
:event e}]
(edit-block! block (if left? 0 :max) opts))))))
(when-not (auto-complete?)
(let [selected-blocks (state/get-selection-blocks)
f (case direction :left first :right last)
node (some-> selected-blocks f)]
(if (some-> node (dom/has-class? "block-add-button"))
(.click node)
(when-let [block-id (some-> node (dom/attr "blockid") uuid)]
(util/stop e)
(let [block {:block/uuid block-id}
left? (= direction :left)
opts {:container-id (some-> node (dom/attr "containerid") (parse-long))
:event e}]
(edit-block! block (if left? 0 :max) opts)))))))
(defn shortcut-left-right [direction]
(fn [e]
@@ -3960,8 +3988,8 @@
(p/do!
(when (seq children)
(if-let [today-last-child (last (ldb/sort-by-order (:block/_parent today)))]
(move-blocks! children today-last-child true)
(move-blocks! children today false)))
(move-blocks! children today-last-child {:sibling? true})
(move-blocks! children today {:sibling? false})))
(state/close-modal!)
(mobile-state/set-popup! nil)
(when (seq children)

View File

@@ -44,13 +44,14 @@
[promesa.core :as p]))
(defmethod events/handle :go/search [_]
(shui/dialog-open!
cmdk/cmdk-modal
{:id :ls-dialog-cmdk
:align :top
:content-props {:class "ls-dialog-cmdk"}
:close-btn? false
:onEscapeKeyDown (fn [e] (.preventDefault e))}))
(when-not (editor-handler/dialog-exists? :ls-dialog-cmdk)
(shui/dialog-open!
cmdk/cmdk-modal
{:id :ls-dialog-cmdk
:align :top
:content-props {:class "ls-dialog-cmdk"}
:close-btn? false
:onEscapeKeyDown (fn [e] (.preventDefault e))})))
(defmethod events/handle :command/run [_]
(when (util/electron?)
@@ -233,8 +234,7 @@
(if target'
(shui/popup-show! target'
#(property-dialog/dialog blocks opts')
{:align "start"
:auto-focus? true})
{:align "start"})
(shui/dialog-open! #(property-dialog/dialog blocks opts')
{:id :property-dialog
:align "start"}))))))

View File

@@ -1,8 +1,8 @@
(ns frontend.log
"System-component-like ns that encapsulates logging functionality"
(:require [lambdaisland.glogi :as log]
[lambdaisland.glogi.console :as glogi-console]
[frontend.config :as config]))
(:require [frontend.config :as config]
[lambdaisland.glogi :as log]
[lambdaisland.glogi.console :as glogi-console]))
;; TODO: Move code below into a fn to behave like a system component
;; instead of having no control over its behavior at require time

View File

@@ -35,11 +35,11 @@
[:delete-blocks [ids opts]]))))
(defn move-blocks!
[blocks target-block sibling?]
[blocks target-block opts]
(op-transact!
(let [ids (map :db/id blocks)
target-id (:db/id target-block)]
[:move-blocks [ids target-id sibling?]])))
[:move-blocks [ids target-id opts]])))
(defn move-blocks-up-down!
[blocks up?]
@@ -73,6 +73,11 @@
(op-transact!
[:delete-property-value [block-eid property-id property-value]]))
(defn batch-delete-property-value!
[block-eids property-id property-value]
(op-transact!
[:batch-delete-property-value [block-eids property-id property-value]]))
(defn create-property-text-block!
[block-id property-id value opts]
(op-transact!

View File

@@ -290,6 +290,8 @@
:editor/move-block-down {:binding (if mac? "mod+shift+down" "alt+shift+down")
:fn (editor-handler/move-up-down false)}
:editor/move-blocks {:binding "mod+shift+m"
:fn editor-handler/move-selected-blocks}
:editor/open-edit {:binding "enter"
:fn (fn [e]
@@ -365,6 +367,11 @@
:fn (fn [e]
(when e (util/stop e))
(state/pub-event! [:editor/new-property {}]))}
:editor/set-tags {:binding "p t"
:db-graph? true
:selection? true
:fn (fn []
(state/pub-event! [:editor/new-property {:property-key "Tags"}]))}
:editor/add-property-deadline {:binding "p d"
:db-graph? true
@@ -390,7 +397,7 @@
:fn (fn []
(state/pub-event! [:editor/new-property {:property-key "Icon"}]))}
:editor/toggle-display-all-properties {:binding "p t"
:editor/toggle-display-all-properties {:binding "p a"
:db-graph? true
:fn ui-handler/toggle-show-empty-hidden-properties!}
@@ -792,6 +799,7 @@
:editor/select-down
:editor/move-block-up
:editor/move-block-down
:editor/move-blocks
:editor/open-edit
:editor/open-selected-blocks-in-sidebar
:editor/select-block-up
@@ -862,6 +870,7 @@
:editor/copy-current-file
:editor/copy-page-url
:editor/new-whiteboard
:editor/set-tags
:editor/add-property-deadline
:editor/add-property-status
:editor/add-property-priority
@@ -968,6 +977,7 @@
:editor/open-link-in-sidebar
:editor/move-block-up
:editor/move-block-down
:editor/move-blocks
:editor/escape-editing]
:shortcut.category/block-command-editing
@@ -996,6 +1006,7 @@
:editor/select-block-down
:editor/delete-selection
:editor/add-property
:editor/set-tags
:editor/add-property-deadline
:editor/add-property-status
:editor/add-property-priority

View File

@@ -103,6 +103,7 @@
;; ui
:ui/viewport {}
:ui/show-property-dialog? (atom false)
;; left sidebar
:ui/navigation-item-collapsed? {}

View File

@@ -295,7 +295,8 @@
(icon "info-circle" {:class "text-indigo-500" :size "20"}))
status)]
[:div.ui__notifications-content
{:style
{:class (str "notification-" (name (or status :info)))
:style
(when (or (= state "exiting")
(= state "exited"))
{:z-index -1})}

View File

@@ -21,7 +21,8 @@
{:page-uuid uuid
:skip-existing-page-check? true})
result (ldb/transact! conn [page] {:persist-op? false
:outliner-op :create-page})]
:outliner-op :create-page
:rtc-op? true})]
[result page-name (:block/uuid page)]))
(defn create!

View File

@@ -12,6 +12,7 @@
[logseq.common.util :as common-util]
[logseq.common.uuid :as common-uuid]
[logseq.db :as ldb]
[logseq.db.common.order :as db-order]
[logseq.db.common.sqlite :as common-sqlite]
[logseq.db.frontend.validate :as db-validate]
[logseq.db.sqlite.export :as sqlite-export]
@@ -126,6 +127,62 @@
(assert (= (count (distinct (map :block/order children))) (count children))
(str ":block/order is not unique for children blocks, parent id: " (:db/id parent))))))))))
(defn- toggle-page-and-block
[conn {:keys [db-before db-after tx-data tx-meta]}]
(when-not (:rtc-op? tx-meta)
(let [page-tag (d/entity @conn :logseq.class/Page)
library-page (ldb/get-library-page db-after)]
(mapcat
(fn [datom]
(let [id (:e datom)
page-tag-update? (and (= :block/tags (:a datom))
(= (:db/id page-tag) (:v datom)))
move-to-library? (and (= :block/parent (:a datom))
(= (:db/id library-page) (:v datom))
(:added datom))]
(when (or page-tag-update? move-to-library?)
(let [block-before (d/entity db-before id)
block-after (d/entity db-after id)]
(when block-after
(cond
;; move non-page block to Library
(and move-to-library? (not (ldb/page? block-after)))
[{:db/id id
:block/name (common-util/page-name-sanity-lc (:block/title block-after))
:block/tags :logseq.class/Page}
[:db/retract id :block/page]]
;; block->page
(and (:added datom) block-before (not (ldb/page? block-before))) ; block->page
(let [->page-tx [{:db/id id
:block/name (common-util/page-name-sanity-lc (:block/title block-after))}
[:db/retract id :block/page]]
move-parent-to-library-tx (let [block (d/entity db-after (:e datom))
block-parent (:block/parent block)]
(assert (ldb/page? block-parent))
(when (and (nil? (:block/parent block-parent))
block-parent
(not= (:db/id block-parent) (:db/id library-page))
(not (:db/ident block-parent))
(not (ldb/built-in? block-parent)))
[{:db/id (:db/id block-parent)
:block/parent (:db/id (ldb/get-library-page db-after))
:block/order (db-order/gen-key)}]))]
(concat ->page-tx move-parent-to-library-tx))
;; page->block
(and block-before (not (:added datom)) (ldb/internal-page? block-before))
(let [parent (:block/parent block-before)
parent-page (when parent
(loop [parent parent]
(if (ldb/page? parent)
parent
(recur (:block/parent parent)))))]
(when parent-page
[[:db/retract id :block/name]
[:db/add id :block/page (:db/id parent-page)]]))))))))
tx-data))))
(defn- add-missing-properties-to-typed-display-blocks
"Add missing properties for these cases:
1. Add corresponding tag when invoking commands like /code block.
@@ -220,22 +277,44 @@
(defn- compute-extra-tx-data
[repo conn tx-report]
(let [{:keys [db-before db-after tx-data tx-meta]} tx-report
toggle-page-and-block-tx-data (toggle-page-and-block conn tx-report)
display-blocks-tx-data (add-missing-properties-to-typed-display-blocks db-after tx-data)
commands-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (:rtc-tx? tx-meta))
(commands/run-commands conn tx-report))
insert-templates-tx (insert-tag-templates repo tx-report)
created-by-tx (add-created-by-ref-hook db-before db-after tx-data tx-meta)]
(concat display-blocks-tx-data commands-tx insert-templates-tx created-by-tx)))
(concat toggle-page-and-block-tx-data display-blocks-tx-data commands-tx insert-templates-tx created-by-tx)))
(defn- undo-tx-data-if-disallowed!
[conn {:keys [tx-data]}]
(let [db @conn
page-has-block-parent? (some (fn [d] (and (:added d)
(= :block/parent (:a d))
(ldb/page? (d/entity db (:e d)))
(not (ldb/page? (d/entity db (:v d)))))) tx-data)]
;; TODO: add other cases that need to be undo
(when page-has-block-parent?
(let [reversed-tx-data (map (fn [[e a v _tx add?]]
(let [op (if add? :db/retract :db/add)]
[op e a v])) tx-data)]
(d/transact! conn reversed-tx-data {:op :undo-tx-data}))
(throw (ex-info "Page can't have block as parent"
{:type :notification
:payload {:message "Page can't have block as parent"
:type :warning}
:tx-data tx-data})))))
(defn- invoke-hooks-default
[repo conn {:keys [tx-meta] :as tx-report} context]
;; Notice: don't catch `undo-tx-data-if-disallowed!` since we want it failed immediately
(undo-tx-data-if-disallowed! conn tx-report)
(try
(let [tx-before-refs (when (sqlite-util/db-based-graph? repo)
(compute-extra-tx-data repo conn tx-report))
tx-report* (if (seq tx-before-refs)
(let [result (ldb/transact! conn tx-before-refs {:pipeline-replace? true
:outliner-op :pre-hook-invoke
:skip-store? true})]
(let [extra-tx-data (when (sqlite-util/db-based-graph? repo)
(compute-extra-tx-data repo conn tx-report))
tx-report* (if (seq extra-tx-data)
(let [result (ldb/transact! conn extra-tx-data {:pipeline-replace? true
:outliner-op :pre-hook-invoke
:skip-store? true})]
(assoc tx-report
:tx-data (concat (:tx-data tx-report) (:tx-data result))
:db-after (:db-after result)))

View File

@@ -82,7 +82,8 @@ so need to pull earlier remote-data from websocket."})
nil sorted-order+block-uuid-coll)]
(index/generate-key-between start-order end-order)
block-order)]
(ldb/transact! conn [{:block/uuid block-uuid :block/order block-order*}])
(ldb/transact! conn [{:block/uuid block-uuid :block/order block-order*}]
{:rtc-op? true})
;; TODO: add ops when block-order* != block-order
))
@@ -113,7 +114,8 @@ so need to pull earlier remote-data from websocket."})
block-parent (assoc :block/parent [:block/uuid block-parent])))
block-uuid+parent-coll)
{:persist-op? false
:gen-undo-ops? false}))
:gen-undo-ops? false
:rtc-op? true}))
(defmethod transact-db! :save-block [_ & args]
(outliner-tx/transact!
@@ -128,11 +130,13 @@ so need to pull earlier remote-data from websocket."})
(ldb/transact! conn
(mapv (fn [block-uuid] [:db/retractEntity [:block/uuid block-uuid]]) block-uuids)
{:persist-op? false
:gen-undo-ops? false}))
:gen-undo-ops? false
:rtc-op? true}))
(defmethod transact-db! :upsert-whiteboard-block [_ conn blocks]
(ldb/transact! conn blocks {:persist-op? false
:gen-undo-ops? false}))
:gen-undo-ops? false
:rtc-op? true}))
(defn- group-remote-remove-ops-by-whiteboard-block
"return {true [<whiteboard-block-ops>], false [<other-ops>]}"
@@ -174,7 +178,7 @@ so need to pull earlier remote-data from websocket."})
(when-let [b (d/entity @conn [:block/uuid block-uuid])]
(when-let [target-b
(d/entity @conn (:db/id (:block/page (d/entity @conn [:block/uuid block-uuid]))))]
(transact-db! :move-blocks&persist-op repo conn [b] target-b false))))
(transact-db! :move-blocks&persist-op repo conn [b] target-b {:sibling? false}))))
(doseq [block-uuid block-uuids-to-remove]
(when-let [b (d/entity @conn [:block/uuid block-uuid])]
(transact-db! :delete-blocks repo conn date-formatter [b] {}))))))
@@ -189,7 +193,7 @@ so need to pull earlier remote-data from websocket."})
(case [whiteboard-page-block? (some? local-parent) (some? remote-block-order)]
[false true true]
(do (if move?
(transact-db! :move-blocks repo conn [b] local-parent false)
(transact-db! :move-blocks repo conn [b] local-parent {:sibling? false})
(transact-db! :insert-blocks repo conn
[{:block/uuid block-uuid
:block/title ""}]
@@ -198,7 +202,8 @@ so need to pull earlier remote-data from websocket."})
[false true false]
(if move?
(transact-db! :move-blocks repo conn [b] local-parent false)
(transact-db! :move-blocks repo conn [b] local-parent
{:sibling? false})
(transact-db! :insert-no-order-blocks conn [[block-uuid first-remote-parent]]))
([true false false] [true false true] [true true false] [true true true])
@@ -494,15 +499,15 @@ so need to pull earlier remote-data from websocket."})
(let [{update-block-order-tx-data :tx-data op-value :op-value} (update-block-order (:db/id ent) op-value)
first-remote-parent (first parents)
local-parent (d/entity @conn [:block/uuid first-remote-parent])
whiteboard-page-block? (ldb/whiteboard? local-parent)]
whiteboard-page-block? (ldb/whiteboard? local-parent)
tx-meta {:persist-op? false :gen-undo-ops? false :rtc-op? true}]
(if whiteboard-page-block?
(upsert-whiteboard-block repo conn op-value)
(do (when-let [schema-tx-data (remote-op-value->schema-tx-data block-uuid op-value)]
(ldb/transact! conn schema-tx-data {:persist-op? false :gen-undo-ops? false}))
(ldb/transact! conn schema-tx-data tx-meta))
(when-let [tx-data (seq (remote-op-value->tx-data @conn ent (dissoc op-value :client/schema)
rtc-const/ignore-attrs-when-syncing))]
(ldb/transact! conn (concat tx-data update-block-order-tx-data)
{:persist-op? false :gen-undo-ops? false}))))))))
(ldb/transact! conn (concat tx-data update-block-order-tx-data) tx-meta))))))))
(defn- apply-remote-update-ops
[repo conn update-ops]

View File

@@ -403,9 +403,13 @@ DROP TRIGGER IF EXISTS blocks_au;
(let [{:keys [id page title snippet]} result
block-id (uuid id)]
(when-let [block (d/entity @conn [:block/uuid block-id])]
(when-not (and library-page-search?
(or (:block/parent block)
(not (ldb/internal-page? block)))) ; remove pages that already have parents
(when-not (or
;; remove pages that already have parents
(and library-page-search?
(or (ldb/page-in-library? @conn block)
(not (ldb/internal-page? block))))
;; remove non-page blocks when asking for pages only
(and page-only? (not (ldb/page? block))))
(when (if dev?
true
(if built-in?

View File

@@ -718,57 +718,57 @@
(defn ^:export insert_block
[block-uuid-or-page-name content ^js opts]
(this-as this
(when (string/blank? block-uuid-or-page-name)
(throw (js/Error. "Page title or block UUID shouldn't be empty.")))
(when (string/blank? block-uuid-or-page-name)
(throw (js/Error. "Page title or block UUID shouldn't be empty.")))
(p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
block (<pull-block (str block-uuid-or-page-name))]
(if (and block? (not block))
(throw (js/Error. "Block not exists"))
(p/let [{:keys [before sibling focus customUUID properties autoOrderedList]} (bean/->clj opts)
[page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
[nil (uuid block-uuid-or-page-name)]
[block-uuid-or-page-name nil])
page-name (when page-name (util/page-name-sanity-lc page-name))
_ (when (and page-name
(nil? (ldb/get-page (db/get-db) page-name)))
(page-handler/<create! block-uuid-or-page-name {}))
custom-uuid (or customUUID (:id properties))
custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
edit-block? (if (nil? focus) true focus)
_ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
(throw (js/Error.
(util/format "Custom block UUID already exists (%s)." custom-uuid))))
block-uuid' (if (and (not sibling) before block-uuid)
(let [block (db/entity [:block/uuid block-uuid])
first-child (ldb/get-first-child (db/get-db) (:db/id block))]
(if first-child
(:block/uuid first-child)
block-uuid))
block-uuid)
insert-at-first-child? (not= block-uuid' block-uuid)
[sibling? before?] (if insert-at-first-child?
[true true]
[sibling before])
db-base? (db-graph?)
before? (if (and (false? sibling?) before? (not insert-at-first-child?))
false
before?)
new-block (editor-handler/api-insert-new-block!
content
{:block-uuid block-uuid'
:sibling? sibling?
:before? before?
:edit-block? edit-block?
:page page-name
:custom-uuid custom-uuid
:ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
:properties (when (not db-base?)
(merge properties
(when custom-uuid {:id custom-uuid})))})
_ (when (and db-base? (some? properties))
(api-block/save-db-based-block-properties! new-block properties this))]
(bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))
(p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
block (<pull-block (str block-uuid-or-page-name))]
(if (and block? (not block))
(throw (js/Error. "Block not exists"))
(p/let [{:keys [before sibling focus customUUID properties autoOrderedList]} (bean/->clj opts)
[page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
[nil (uuid block-uuid-or-page-name)]
[block-uuid-or-page-name nil])
page-name (when page-name (util/page-name-sanity-lc page-name))
_ (when (and page-name
(nil? (ldb/get-page (db/get-db) page-name)))
(page-handler/<create! block-uuid-or-page-name {}))
custom-uuid (or customUUID (:id properties))
custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
edit-block? (if (nil? focus) true focus)
_ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
(throw (js/Error.
(util/format "Custom block UUID already exists (%s)." custom-uuid))))
block-uuid' (if (and (not sibling) before block-uuid)
(let [block (db/entity [:block/uuid block-uuid])
first-child (ldb/get-first-child (db/get-db) (:db/id block))]
(if first-child
(:block/uuid first-child)
block-uuid))
block-uuid)
insert-at-first-child? (not= block-uuid' block-uuid)
[sibling? before?] (if insert-at-first-child?
[true true]
[sibling before])
db-base? (db-graph?)
before? (if (and (false? sibling?) before? (not insert-at-first-child?))
false
before?)
new-block (editor-handler/api-insert-new-block!
content
{:block-uuid block-uuid'
:sibling? sibling?
:before? before?
:edit-block? edit-block?
:page page-name
:custom-uuid custom-uuid
:ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
:properties (when (not db-base?)
(merge properties
(when custom-uuid {:id custom-uuid})))})
_ (when (and db-base? (some? properties))
(api-block/save-db-based-block-properties! new-block properties this))]
(bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))
(def ^:export insert_batch_block
(fn [block-uuid ^js batch-blocks ^js opts]
@@ -892,11 +892,11 @@
(defn ^:export get_property
[k]
(this-as this
(p/let [prop (-get-property this k)]
(some-> prop
(assoc :type (:logseq.property/type prop))
(sdk-utils/normalize-keyword-for-json)
(bean/->js)))))
(p/let [prop (-get-property this k)]
(some-> prop
(assoc :type (:logseq.property/type prop))
(sdk-utils/normalize-keyword-for-json)
(bean/->js)))))
(defn ^:export upsert_property
"schema:
@@ -920,7 +920,7 @@
schema (cond-> schema
(string? (:cardinality schema))
(-> (assoc :db/cardinality (keyword (:cardinality schema)))
(dissoc :cardinality))
(dissoc :cardinality))
(string? (:type schema))
(-> (assoc :logseq.property/type (keyword (:type schema)))
@@ -935,70 +935,70 @@
(defn ^:export remove_property
[k]
(this-as this
(p/let [prop (-get-property this k)]
(when-let [uuid (and (api-block/plugin-property-key? (:db/ident prop))
(:block/uuid prop))]
(page-common-handler/<delete! uuid nil nil)))))
(p/let [prop (-get-property this k)]
(when-let [uuid (and (api-block/plugin-property-key? (:db/ident prop))
(:block/uuid prop))]
(page-common-handler/<delete! uuid nil nil)))))
;; block properties
(defn ^:export upsert_block_property
[block-uuid key ^js value]
(this-as this
(p/let [keyname (api-block/sanitize-user-property-name key)
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
repo (state/get-current-repo)
block (db-async/<get-block repo block-uuid :children? false)
db-base? (db-graph?)
key' (-> (if (keyword? keyname) (name keyname) keyname) (util/trim-safe))
value (bean/->clj value)]
(when block
(if db-base?
(p/do!
(api-block/save-db-based-block-properties! block {key' value} this))
(property-handler/set-block-property! repo block-uuid key' value))))))
(p/let [keyname (api-block/sanitize-user-property-name key)
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
repo (state/get-current-repo)
block (db-async/<get-block repo block-uuid :children? false)
db-base? (db-graph?)
key' (-> (if (keyword? keyname) (name keyname) keyname) (util/trim-safe))
value (bean/->clj value)]
(when block
(if db-base?
(p/do!
(api-block/save-db-based-block-properties! block {key' value} this))
(property-handler/set-block-property! repo block-uuid key' value))))))
(defn ^:export remove_block_property
[block-uuid key]
(this-as this
(p/let [key (api-block/sanitize-user-property-name key)
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
_ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)
db? (config/db-based-graph? (state/get-current-repo))
key-ns? (and (keyword? key) (namespace key))
key (if key-ns? key (if (keyword? key) (name key) key))
key (if (and db? (not key-ns?))
(api-block/get-db-ident-for-user-property-name
key (api-block/resolve-property-prefix-for-db this))
key)]
(property-handler/remove-block-property!
(state/get-current-repo)
block-uuid key))))
(p/let [key (api-block/sanitize-user-property-name key)
block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
_ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)
db? (config/db-based-graph? (state/get-current-repo))
key-ns? (and (keyword? key) (namespace key))
key (if key-ns? key (if (keyword? key) (name key) key))
key (if (and db? (not key-ns?))
(api-block/get-db-ident-for-user-property-name
key (api-block/resolve-property-prefix-for-db this))
key)]
(property-handler/remove-block-property!
(state/get-current-repo)
block-uuid key))))
(defn ^:export get_block_property
[block-uuid key]
(this-as this
(p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
_ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)]
(when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
(when (seq properties)
(let [key (api-block/sanitize-user-property-name key)
property-name (if (keyword? key) (name key) key)
ident (api-block/get-db-ident-for-user-property-name
property-name (api-block/resolve-property-prefix-for-db this))
property-value (or (get properties key)
(get properties (keyword property-name))
(get properties ident))
property-value (if-let [property-id (:db/id property-value)]
(db/pull property-id) property-value)
property-value (cond-> property-value
(map? property-value)
(assoc
:value (or (:logseq.property/value property-value)
(:block/title property-value))
:ident ident))
parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
(or parsed-value
(bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
(p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
_ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)]
(when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
(when (seq properties)
(let [key (api-block/sanitize-user-property-name key)
property-name (if (keyword? key) (name key) key)
ident (api-block/get-db-ident-for-user-property-name
property-name (api-block/resolve-property-prefix-for-db this))
property-value (or (get properties key)
(get properties (keyword property-name))
(get properties ident))
property-value (if-let [property-id (:db/id property-value)]
(db/pull property-id) property-value)
property-value (cond-> property-value
(map? property-value)
(assoc
:value (or (:logseq.property/value property-value)
(:block/title property-value))
:ident ident))
parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
(or parsed-value
(bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
(def ^:export get_block_properties
(fn [block-uuid]

View File

@@ -24,7 +24,7 @@
(defn convert?to-built-in-property-name
[property-name]
(if (and (not (qualified-keyword? property-name))
(contains? #{:background-color} property-name))
(contains? #{:background-color} property-name))
(keyword :logseq.property property-name)
property-name))
@@ -32,19 +32,19 @@
[k]
(if (string? k)
(-> k (string/trim)
(string/replace " " "")
(string/replace #"^[:_\s]+" "")
(#(cond-> %
(not (string/includes? % "/"))
(string/lower-case))))
(string/replace " " "")
(string/replace #"^[:_\s]+" "")
(#(cond-> %
(not (string/includes? % "/"))
(string/lower-case))))
k))
(defn resolve-property-prefix-for-db
[^js plugin]
(->> (when (some-> js/window.LSPlugin (.-PluginLocal))
(or (some->> plugin (.-id) (sanitize-user-property-name) (str "."))
"._api"))
(str "plugin.property")))
"._api"))
(str "plugin.property")))
;; FIXME: This ns should not be creating idents. This allows for ident conflicts
;; and assumes that names directly map to idents which is incorrect and breaks for multiple
@@ -58,27 +58,27 @@
(keyword property-name) property-name)
property-name' (convert?to-built-in-property-name property-name')]
(if (qualified-keyword? property-name') property-name'
(db-ident/create-db-ident-from-name prefix (name property-name) false)))))
(db-ident/create-db-ident-from-name prefix (name property-name) false)))))
(defn plugin-property-key?
[ident]
(some-> ident (str)
(string/starts-with? ":plugin.property.")))
(string/starts-with? ":plugin.property.")))
(defn into-readable-db-properties
[properties]
(some-> properties
(db-pu/readable-properties
{:original-key? true :key-fn str})))
(db-pu/readable-properties
{:original-key? true :key-fn str})))
(defn into-properties
([block] (into-properties (state/get-current-repo) block))
([repo block]
(if (some-> repo (config/db-based-graph?))
(let [props (some->> block
(filter (fn [[k _]] (db-property/property? k)))
(into {})
(into-readable-db-properties))
(filter (fn [[k _]] (db-property/property? k)))
(into {})
(into-readable-db-properties))
block (update block :block/properties merge props)
block (apply dissoc (concat [block] (keys props)))]
block)
@@ -87,8 +87,8 @@
(defn parse-property-json-value-if-need
[ident property-value]
(when-let [prop (and (string? property-value)
(plugin-property-key? ident)
(some-> ident (db-utils/entity)))]
(plugin-property-key? ident)
(some-> ident (db-utils/entity)))]
(if (= (:logseq.property/type prop) :string)
(try
(js/JSON.parse property-value)
@@ -108,10 +108,10 @@
(let [id (:db/id (ldb/get-case-page (conn/get-db) page))]
(if (nil? id)
(-> (page-handler/<create! page {:redirect? false})
(p/then #(:db/id %)))
(p/then #(:db/id %)))
id))))
(p/all)
(p/then (fn [vs] [ident :logseq.property/empty-placeholder vs true])))
(p/all)
(p/then (fn [vs] [ident :logseq.property/empty-placeholder vs true])))
(let [value (if as-json? (js/JSON.stringify (bean/->js value)) value)]
[ident value nil false]))))
ent (db-utils/entity ident)]
@@ -124,8 +124,8 @@
schema {:logseq.property/type type
:db/cardinality :one}]
(p/chain
(db-property-handler/upsert-property! ident schema {})
(fn [] (value-handle type false))))
(db-property-handler/upsert-property! ident schema {})
(fn [] (value-handle type false))))
(let [value-multi? (vector? value)
ident (:db/ident ent)
ent-type (:logseq.property/type ent)
@@ -135,54 +135,54 @@
(when cardinality-want-illegal-changed?
(throw (js/Error. "Multiple property type can not be changed.")))
(p/chain
(db-property-handler/upsert-property! ident
{:logseq.property/type ent-type
:db/cardinality (if (and (not ent-type-str?) value-multi?) :many :one)}
{})
#(value-handle ent-type ent-multi?))))))
(db-property-handler/upsert-property! ident
{:logseq.property/type ent-type
:db/cardinality (if (and (not ent-type-str?) value-multi?) :many :one)}
{})
#(value-handle ent-type ent-multi?))))))
(defn save-db-based-block-properties!
([block properties] (save-db-based-block-properties! block properties nil))
([block properties ^js plugin]
(when-let [block-id (and (seq properties) (:db/id block))]
(let [properties (update-keys properties
(fn [k]
(let [prefix (resolve-property-prefix-for-db plugin)]
(get-db-ident-for-user-property-name k prefix))))
(fn [k]
(let [prefix (resolve-property-prefix-for-db plugin)]
(get-db-ident-for-user-property-name k prefix))))
*properties-page-refs (volatile! {})]
(-> (for [ident (keys properties)]
(p/let [ret (infer-property-value-type-to-save! ident (get properties ident))]
ret))
(p/all)
(p/chain
(fn [props]
(->> props
(reduce (fn [a [k v vs multi?]]
(if multi?
(do (vswap! *properties-page-refs assoc k vs) a)
(assoc a k v))) {})
(db-property-handler/set-block-properties! block-id)))
(p/all)
(p/chain
(fn [props]
(->> props
(reduce (fn [a [k v vs multi?]]
(if multi?
(do (vswap! *properties-page-refs assoc k vs) a)
(assoc a k v))) {})
(db-property-handler/set-block-properties! block-id)))
;; handle page refs
(fn []
(when (seq @*properties-page-refs)
(doseq [[ident refs] @*properties-page-refs]
(-> (property-handler/remove-block-property! (state/get-current-repo) block-id ident)
(p/then
(fn []
(if (seq refs)
(ui-outliner-tx/transact!
{:outliner-op :set-block-properties}
(doseq [eid refs]
(when (number? eid)
(property-handler/set-block-property!
(fn []
(when (seq @*properties-page-refs)
(doseq [[ident refs] @*properties-page-refs]
(-> (property-handler/remove-block-property! (state/get-current-repo) block-id ident)
(p/then
(fn []
(if (seq refs)
(ui-outliner-tx/transact!
{:outliner-op :set-block-properties}
(doseq [eid refs]
(when (number? eid)
(property-handler/set-block-property!
(state/get-current-repo) block-id ident eid))))
(db-property-handler/set-block-property! block-id ident :logseq.property/empty-placeholder))))))))))))))
(db-property-handler/set-block-property! block-id ident :logseq.property/empty-placeholder))))))))))))))
(defn <sync-children-blocks!
[block]
(when block
(db-async/<get-block (state/get-current-repo)
(:block/uuid (:block/parent block)) {:children? true})))
(:block/uuid (:block/parent block)) {:children? true})))
(defn get_block
[id-or-uuid ^js opts]
@@ -190,19 +190,19 @@
(db-utils/pull id-or-uuid)
(and id-or-uuid (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error id-or-uuid))))]
(when (or (true? (some-> opts (.-includePage)))
(not (contains? block :block/name)))
(not (contains? block :block/name)))
(when-let [uuid (:block/uuid block)]
(let [{:keys [includeChildren]} (bean/->clj opts)
repo (state/get-current-repo)
block (if includeChildren
;; nested children results
(let [blocks (->> (db-model/get-block-and-children repo uuid)
(map (fn [b]
(dissoc (db-utils/pull (:db/id b)) :block.temp/load-status))))]
(map (fn [b]
(dissoc (db-utils/pull (:db/id b)) :block.temp/load-status))))]
(first (outliner-tree/blocks->vec-tree blocks uuid)))
;; attached shallow children
(assoc block :block/children
(map #(list :uuid (:block/uuid %))
(db/get-block-immediate-children repo uuid))))
(map #(list :uuid (:block/uuid %))
(db/get-block-immediate-children repo uuid))))
block (into-properties repo block)]
(bean/->js (sdk-utils/normalize-keyword-for-json block)))))))

View File

@@ -57,6 +57,6 @@
[:span.text-muted-foreground {:slot "icon-only"}
(ion/tabler-icon "dots" {:size 20})])))))
(ion/content {:class "ion-padding"}
(user-profile login?)
[:div.mt-8
(repo/repos-cp)]))))
(user-profile login?)
[:div.mt-8
(repo/repos-cp)]))))