enhance: auto-convert text to block

pull/10438/head
Tienson Qin 2023-09-12 16:18:19 +08:00
parent 5162598665
commit da5d8ef25b
4 changed files with 130 additions and 216 deletions

View File

@ -90,7 +90,7 @@
(reset! (::property-name state) (:block/original-name property)) (reset! (::property-name state) (:block/original-name property))
(reset! (::property-schema state) (:block/schema property)) (reset! (::property-schema state) (:block/schema property))
state))} state))}
[state repo property {:keys [toggle-fn block] :as opts}] [state repo property {:keys [toggle-fn] :as opts}]
(let [*property-name (::property-name state) (let [*property-name (::property-name state)
*property-schema (::property-schema state) *property-schema (::property-schema state)
built-in-property? (contains? db-property/built-in-properties-keys-str (:block/original-name property)) built-in-property? (contains? db-property/built-in-properties-keys-str (:block/original-name property))
@ -137,7 +137,7 @@
[:label "Specify classes:"] [:label "Specify classes:"]
(class-select *property-schema (:classes @*property-schema) opts)]) (class-select *property-schema (:classes @*property-schema) opts)])
(when-not (= (:type @*property-schema) :checkbox) (when-not (contains? #{:checkbox :default} (:type @*property-schema))
[:div.grid.grid-cols-4.gap-1.items-center.leading-8 [:div.grid.grid-cols-4.gap-1.items-center.leading-8
[:label "Multiple values:"] [:label "Multiple values:"]
(let [many? (boolean (= :many (:cardinality @*property-schema)))] (let [many? (boolean (= :many (:cardinality @*property-schema)))]
@ -168,16 +168,13 @@
(ui/button (ui/button
"Save" "Save"
:on-click (fn [e] :on-click (fn [e]
(let [block? (= :block (:type @*property-schema))] (util/stop e)
(util/stop e) (property-handler/update-property!
(property-handler/update-property! repo (:block/uuid property)
repo (:block/uuid property) {:property-name @*property-name
{:property-name @*property-name :property-schema @*property-schema})
:property-schema @*property-schema}) (state/close-modal!)
(state/close-modal!) (when toggle-fn (toggle-fn)))))]]]))
(when toggle-fn (toggle-fn))
(when (and block? block)
(pv/create-new-block! block property nil))))))]]]))
(defn- get-property-from-db [name] (defn- get-property-from-db [name]
(when-not (string/blank? name) (when-not (string/blank? name)
@ -390,7 +387,9 @@
(for [[k v] properties] (for [[k v] properties]
(when (uuid? k) (when (uuid? k)
(when-let [property (db/sub-block (:db/id (db/entity [:block/uuid k])))] (when-let [property (db/sub-block (:db/id (db/entity [:block/uuid k])))]
(let [block? (= :block (get-in property [:block/schema :type]))] (let [block? (and (= :default (get-in property [:block/schema :type]))
(uuid? v)
(db/entity [:block/uuid v]))]
[:div {:class (if block? [:div {:class (if block?
"flex flex-1 flex-col gap-1" "flex flex-1 flex-col gap-1"
"property-pair items-center")} "property-pair items-center")}

View File

@ -96,7 +96,6 @@
(model/get-all-page-original-names repo)) (model/get-all-page-original-names repo))
distinct) distinct)
options (map (fn [p] {:value p}) pages) options (map (fn [p] {:value p}) pages)
type (:type (:block/schema property))
opts {:items options opts {:items options
:dropdown? true :dropdown? true
:input-default-placeholder (if multiple-values? :input-default-placeholder (if multiple-values?
@ -133,94 +132,94 @@
nil))})}] nil))})}]
(select/select opts))) (select/select opts)))
(defn- move-cursor ;; (defn- move-cursor
[up? opts] ;; [up? opts]
(let [f (if up? dec inc) ;; (let [f (if up? dec inc)
id (str (:parent-dom-id opts) "-" (f (:idx opts))) ;; id (str (:parent-dom-id opts) "-" (f (:idx opts)))
editor-id (str (:parent-dom-id opts) "-editor" "-" (f (:idx opts))) ;; editor-id (str (:parent-dom-id opts) "-editor" "-" (f (:idx opts)))
sibling (gdom/getElement id) ;; sibling (gdom/getElement id)
editor (gdom/getElement editor-id)] ;; editor (gdom/getElement editor-id)]
(when sibling ;; (when sibling
(.click sibling) ;; (.click sibling)
(state/set-state! :editor/property-triggered-by-click? {editor-id true})) ;; (state/set-state! :editor/property-triggered-by-click? {editor-id true}))
(when editor ;; (when editor
(.focus editor)))) ;; (.focus editor))))
(defn- save-text!
[repo block property value editor-id e]
(let [new-value (util/evalue e)
blank? (string/blank? new-value)]
(when (not (state/get-editor-action))
(util/stop e)
(when-not blank?
(when (not= (string/trim new-value) (and value (string/trim value)))
(property-handler/set-block-property! repo (:block/uuid block)
(:block/original-name property)
new-value
:old-value value)))
(when (= js/document.activeElement (gdom/getElement editor-id))
(exit-edit-property)))))
(defn create-new-block!
[block property value]
(let [repo (state/get-current-repo)
pid (:block/uuid (db/entity [:block/name "created-in-property"]))
page-id (or (:db/id (:block/page block)) (:db/id block))
parent-id (db/new-block-id)
parent (-> {:block/uuid parent-id
:block/format :markdown
:block/content ""
:block/page {:db/id page-id}
:block/properties {pid true}}
outliner-core/block-with-timestamps)
child-1-id (db/new-block-id)
child-1 (-> {:block/uuid child-1-id
:block/format :markdown
:block/content value
:block/page {:db/id page-id}
:block/parent [:block/uuid parent-id]
:block/left [:block/uuid parent-id]
:block/properties {pid true}}
outliner-core/block-with-timestamps
(editor-handler/wrap-parse-block))
child-2-id (db/new-block-id)
child-2 (-> {:block/uuid child-2-id
:block/format :markdown
:block/content ""
:block/page {:db/id page-id}
:block/parent [:block/uuid parent-id]
:block/left [:block/uuid child-1-id]
:block/properties {pid true}}
outliner-core/block-with-timestamps)
tx-data [parent child-1 child-2]]
(db/transact! repo tx-data {:outliner-op :insert-blocks})
(add-property! block (:block/original-name property) parent-id)
(editor-handler/edit-block! (db/entity [:block/uuid child-2-id]) 0 child-2-id)))
(defn- new-text-editor-opts (defn- new-text-editor-opts
[repo block property value type editor-id *add-new-item? opts] [repo block property value editor-id]
{:style {:padding 0 {:style {:padding 0
:background "none"} :background "none"}
:on-blur :on-blur
(fn [e] (fn [e]
(let [new-value (util/evalue e) (save-text! repo block property value editor-id e))
blank? (string/blank? new-value)]
(when (not (state/get-editor-action))
(util/stop e)
(when-not blank?
(property-handler/set-block-property! repo (:block/uuid block)
(:block/original-name property)
new-value
:old-value value))
(when (= js/document.activeElement (gdom/getElement editor-id))
(exit-edit-property))
(when *add-new-item? (reset! *add-new-item? false)))))
:on-key-down :on-key-down
(fn [e] (fn [e]
(let [new-value (util/evalue e) (let [enter? (= (util/ekey e) "Enter")
blank? (string/blank? new-value)
enter? (= (util/ekey e) "Enter")
esc? (= (util/ekey e) "Escape") esc? (= (util/ekey e) "Escape")
meta? (util/meta-key? e) backspace? (= (util/ekey e) "Backspace")
create-another-one? (and meta? enter?) new-value (util/evalue e)]
down? (= (util/ekey e) "ArrowDown") (when (and (or enter? esc? backspace?)
up? (= (util/ekey e) "ArrowUp")
backspace? (= (util/ekey e) "Backspace")]
(when (and (or enter? esc? create-another-one? down? up? backspace?)
(not (state/get-editor-action))) (not (state/get-editor-action)))
(when-not (or down? up? backspace?) (util/stop e)) (when-not backspace? (util/stop e))
(when (and (not blank?) (or enter? esc?))
(when (not= (string/trim new-value) (and value (string/trim value)))
(property-handler/set-block-property! repo (:block/uuid block)
(:block/original-name property)
new-value
:old-value value)))
(when-not backspace? (exit-edit-property))
(cond (cond
(and backspace? (= new-value "") *add-new-item? (not @*add-new-item?)) ; delete item esc?
(do (save-text! repo block property value editor-id e)
(move-cursor true opts)
(property-handler/delete-property-value! repo block (:block/uuid property) value))
(and backspace? (= new-value "") *add-new-item? @*add-new-item?) enter?
(do (if (string/blank? value)
(move-cursor true opts) (save-text! repo block property value editor-id e)
(reset! *add-new-item? false)) (create-new-block! block property new-value))))))})
backspace?
nil
down?
(move-cursor false opts)
up?
(move-cursor true opts)
(or
esc?
blank?
(and *add-new-item? (not= type :default)))
(when *add-new-item? (reset! *add-new-item? false))
(and *add-new-item? @*add-new-item?)
(some-> (gdom/getElement editor-id)
(util/set-change-value ""))
(and (or enter? create-another-one?)
*add-new-item?
(not blank?))
(reset! *add-new-item? true)))))})
(defn- select (defn- select
[block property opts] [block property opts]
@ -250,57 +249,28 @@
(when-let [f (:on-chosen opts)] (f))) (when-let [f (:on-chosen opts)] (f)))
nil))})}))) nil))})})))
(defn create-new-block! (rum/defc property-block-value < rum/reactive
[block property {:keys [*add-new-item?]}] [parent block-cp editor-box opts]
(let [repo (state/get-current-repo) (when parent
pid (:block/uuid (db/entity [:block/name "created-in-property"])) (let [children (:block/_parent (db/sub-block (:db/id parent)))]
new-block (-> {:block/uuid (db/new-block-id) (when (empty? children)
:block/format :markdown (let [repo (state/get-current-repo)
:block/content "" pid (:block/uuid (db/entity [:block/name "created-in-property"]))
:block/page {:db/id child-1-id (db/new-block-id)
(or (:db/id (:block/page block)) child-1 (-> {:block/uuid child-1-id
(:db/id block))} :block/format :markdown
:block/properties {pid true}} :block/content ""
outliner-core/block-with-timestamps) :block/page (:db/id (:block/page parent))
id (:block/uuid new-block)] :block/parent (:db/id parent)
(db/transact! repo [new-block] {:outliner-op :insert-blocks}) :block/left (:db/id parent)
(add-property! block (:block/original-name property) id) :block/properties {pid true}}
(when *add-new-item? (reset! *add-new-item? false)) outliner-core/block-with-timestamps)]
(editor-handler/edit-block! (db/entity [:block/uuid id]) 0 id))) (db/transact! repo [child-1] {:outliner-op :insert-blocks})))
[:div.property-block-container.w-full
(defn- block-editor-opts (block-cp children {:id (str (:block/uuid parent))
[repo block property value *add-new-item? opts] :blocks-container-id (:blocks-container-id opts)
{:on-key-down :editor-box editor-box
(fn [e] :in-property? true})])))
(let [new-value (util/evalue e)
meta? (util/meta-key? e)
enter? (= (util/ekey e) "Enter")
create-another-one? (and meta? enter?)
backspace? (= (util/ekey e) "Backspace")
current-block (db/entity [:block/uuid value])]
(cond
create-another-one?
(do
(util/stop e)
(reset! *add-new-item? true)
(create-new-block! block property opts))
(and backspace?
(= new-value "")
;; no children block
(empty? (:block/_parent current-block)))
(do
(editor-handler/keydown-up-down-handler :up {:pos :max})
(property-handler/delete-property-value! repo block (:block/uuid property) value)))))})
(rum/defc empty-block
[on-click]
[:div.flex.flex-row.cursor-text {:on-click on-click}
[:div.flex.flex-row.items-center.mr-2.ml-1
[:span.bullet-container.cursor
[:span.bullet]]]
[:div.flex.flex-1
[:span.opacity-70 ""]]])
(rum/defc property-scalar-value < rum/reactive db-mixins/query (rum/defc property-scalar-value < rum/reactive db-mixins/query
[block property value {:keys [inline-text page-cp block-cp [block property value {:keys [inline-text page-cp block-cp
@ -347,7 +317,7 @@
:block :block
nil nil
(let [config {:editor-opts (new-text-editor-opts repo block property value type editor-id *add-new-item? opts)}] (let [config {:editor-opts (new-text-editor-opts repo block property value editor-id)}]
[:div [:div
(editor-box editor-args editor-id (cond-> config (editor-box editor-args editor-id (cond-> config
multiple-values? multiple-values?
@ -369,25 +339,19 @@
type) type)
type)] type)]
(if (string/blank? value) (if (string/blank? value)
[:div.opacity-70.text-sm [:div.opacity-70
(case type (case type
:page :page
(if multiple-values? "Choose pages" "Choose page") (if multiple-values? "Choose pages" "Choose page")
"Input something")] "Empty")]
(case type (case type
:page :page
(when-let [page (db/entity [:block/uuid value])] (when-let [page (db/entity [:block/uuid value])]
(page-cp {:disable-preview? true} page)) (page-cp {:disable-preview? true} page))
:block :block
(if-let [item-block (db/entity [:block/uuid value])] (if-let [parent (db/entity [:block/uuid value])]
(let [editor-opts (block-editor-opts repo block property value *add-new-item? opts)] (property-block-value parent block-cp editor-box opts)
[:div.property-block-container.w-full
(block-cp [item-block] {:id (str value)
:blocks-container-id (:blocks-container-id opts)
:editor-box editor-box
:editor-opts editor-opts
:in-property? true})])
(do (do
(if multiple-values? (if multiple-values?
(property-handler/delete-property-value! repo block (:block/uuid property) value) (property-handler/delete-property-value! repo block (:block/uuid property) value)
@ -399,25 +363,6 @@
(inline-text {} :markdown (str value)))))]))))) (inline-text {} :markdown (str value)))))])))))
(rum/defc multiple-blocks-add-button < rum/reactive
[block property opts]
(let [editing? (state/sub :editor/editing)]
(when-not editing?
[:div.absolute.fade-in
{:style {:left "-1.75rem"
:bottom " -0.125rem"}
:on-click (fn []
(create-new-block! block property opts))}
(ui/tippy {:html [:span.text-sm
[:span.mr-1 "Add another block (click or "]
(ui/render-keyboard-shortcut "mod+enter")
[:span.ml-1 ")"]]
:interactive true
:delay [100, 100]
:position "bottom"}
[:a.add-button-link.flex.p-2
(ui/icon "circle-plus")])])))
(rum/defc delete-value-button < rum/reactive (rum/defc delete-value-button < rum/reactive
[entity property item] [entity property item]
(let [editing? (state/sub :editor/editing)] (let [editing? (state/sub :editor/editing)]
@ -461,8 +406,6 @@
(let [*show-add? (::show-add? state) (let [*show-add? (::show-add? state)
*add-new-item? (::add-new-item? state) *add-new-item? (::add-new-item? state)
type (:type schema) type (:type schema)
block? (= type :block)
default? (= type :default)
row? (contains? #{:page} type) row? (contains? #{:page} type)
items (if (coll? v) v (when v [v]))] items (if (coll? v) v (when v [v]))]
[:div.relative [:div.relative
@ -471,8 +414,6 @@
(cond-> "flex flex-1 flex-row items-center flex-wrap" (cond-> "flex flex-1 flex-row items-center flex-wrap"
row? row?
(str " gap-2")) (str " gap-2"))
block?
"grid"
:else :else
"grid gap-1") "grid gap-1")
:on-mouse-over #(reset! *show-add? true) :on-mouse-over #(reset! *show-add? true)
@ -506,17 +447,10 @@
:editing? true :editing? true
:*add-new-item? *add-new-item?})) :*add-new-item? *add-new-item?}))
(and (or default? block?) (empty? items))
[:div.rounded-sm {:on-click (fn [] (reset! *add-new-item? true))}
[:div.opacity-50.text-sm "Input something"]]
(and (or @*show-add? (empty? items)) row? (not config/publishing?)) (and (or @*show-add? (empty? items)) row? (not config/publishing?))
[:a.add-button-link.flex [:a.add-button-link.flex
{:on-click (fn [] (reset! *add-new-item? true))} {:on-click (fn [] (reset! *add-new-item? true))}
(ui/icon "circle-plus")] (ui/icon "circle-plus")])]))
(and @*show-add? block? (not config/publishing?))
(multiple-blocks-add-button block property opts))]))
(rum/defc property-value < rum/reactive (rum/defc property-value < rum/reactive
[block property v opts] [block property v opts]
@ -529,11 +463,6 @@
:parent-block block :parent-block block
:format :markdown}] :format :markdown}]
(cond (cond
(and (= type :block) (or (string/blank? v) (empty? v)))
(empty-block (fn [e]
(util/stop e)
(create-new-block! block property {})))
multiple-values? multiple-values?
(multiple-values block property v opts dom-id schema editor-id editor-args) (multiple-values block property v opts dom-id schema editor-id editor-args)

View File

@ -21,13 +21,14 @@
(when-let [e (db/entity [:block/uuid id])] (when-let [e (db/entity [:block/uuid id])]
(nil? (:block/page e))))) (nil? (:block/page e)))))
(defn- logseq-block? (defn- text-or-uuid?
[id] [value]
(and (uuid? id) (or (string? value) (uuid? value)))
(some? (db/entity [:block/uuid id]))))
(def builtin-schema-types (def builtin-schema-types
{:default string? ; refs/tags will not be extracted {:default [:fn
{:error/message "should be a text or UUID"}
text-or-uuid?] ; refs/tags will not be extracted
:number number? :number number?
:date [:fn :date [:fn
{:error/message "should be a journal date"} {:error/message "should be a journal date"}
@ -39,9 +40,6 @@
:page [:fn :page [:fn
{:error/message "should be a page"} {:error/message "should be a page"}
logseq-page?] logseq-page?]
:block [:fn
{:error/message "should be a block"}
logseq-block?]
;; internal usage ;; internal usage
:keyword keyword? :keyword keyword?
:map map? :map map?
@ -50,7 +48,7 @@
:any some?}) :any some?})
(def internal-builtin-schema-types #{:keyword :map :coll :any}) (def internal-builtin-schema-types #{:keyword :map :coll :any})
(def user-face-builtin-schema-types [:default :number :date :checkbox :url :page :block]) (def user-face-builtin-schema-types [:default :number :date :checkbox :url :page])
;; schema -> type, cardinality, object's class ;; schema -> type, cardinality, object's class
;; min, max -> string length, number range, cardinality size limit ;; min, max -> string length, number range, cardinality size limit
@ -74,7 +72,7 @@
v-str v-str
(case schema-type (case schema-type
(:default :any :url) (:default :any :url)
v-str (if (util/uuid-string? v-str) (uuid v-str) v-str)
:number :number
(edn/read-string v-str) (edn/read-string v-str)
@ -85,9 +83,6 @@
:page :page
(uuid v-str) (uuid v-str)
:block
(uuid v-str)
:date :date
v-str ; uuid v-str ; uuid
))) )))
@ -173,7 +168,10 @@
new-value) new-value)
block-properties (assoc properties property-uuid new-value) block-properties (assoc properties property-uuid new-value)
refs (outliner-core/rebuild-block-refs block block-properties)] refs (outliner-core/rebuild-block-refs block block-properties)]
;; TODO: fix block/properties-order (util/pprint [[:db/retract (:db/id block) :block/refs]
{:block/uuid (:block/uuid block)
:block/properties block-properties
:block/refs refs}])
(db/transact! repo (db/transact! repo
[[:db/retract (:db/id block) :block/refs] [[:db/retract (:db/id block) :block/refs]
{:block/uuid (:block/uuid block) {:block/uuid (:block/uuid block)

View File

@ -44,25 +44,13 @@
(defn will-unmount (defn will-unmount
[state] [state]
(let [{:keys [block value node]} (get-state) (let [{:keys [block value]} (get-state)]
property? (= "property" (:block/type block))
repo (state/get-current-repo)]
(editor-handler/clear-when-saved!) (editor-handler/clear-when-saved!)
(when (and (when (and
(not (contains? #{:insert :indent-outdent :auto-save :undo :redo :delete} (state/get-editor-op))) (not (contains? #{:insert :indent-outdent :auto-save :undo :redo :delete} (state/get-editor-op)))
;; Don't trigger auto-save if the latest op is undo or redo ;; Don't trigger auto-save if the latest op is undo or redo
(not (contains? #{:undo :redo} (state/get-editor-latest-op)))) (not (contains? #{:undo :redo} (state/get-editor-latest-op))))
(if property? (editor-handler/save-block! (get-state) value)))
;; click outside property value to save it
(let [parent-block (when-let [id (d/attr node "parentblockid")]
(when (util/uuid-string? id)
(db/entity [:block/uuid (uuid id)])))
property (:block/name block)
old-value (:property-value (last (state/get-editor-args)))]
(when (and parent-block property)
(property-handler/set-block-property! repo (:block/uuid parent-block) property value
:old-value old-value)))
(editor-handler/save-block! (get-state) value))))
state) state)
(def lifecycle (def lifecycle