Browse Source

Enhance context menu (#8930)

* replace duplicate code

* only select current block during open contxt menu & fix issue 7689

* change unnecessary mixin function to value

* add bgcolor to custom context menu

* add collapse/expand to custom context menu

* remove redundant code

* fix misdirected comment

* add make card to custom context menu

* add heading to custom context menu

* Merge branch 'master' into enhance/context-menu

* fix undo heading need multiple times

* Revert "Merge branch 'master' into enhance/context-menu"

This reverts commit d4222db6bb2c191ec94946b2474f8fad7f68885d.

* fix: lint

* fix heading case logically incorrect

* format code

---------

Co-authored-by: Tienson Qin <[email protected]>
megayu 2 years ago
parent
commit
81c1397106

+ 1 - 1
src/main/frontend/components/command_palette.cljs

@@ -33,7 +33,7 @@
         [:code.ml-1 first-shortcut])]]))
 
 (rum/defcs command-palette <
-  (shortcut/disable-all-shortcuts)
+  shortcut/disable-all-shortcuts
   (rum/local "" ::input)
   [state {:keys [commands limit]
           :or {limit 100}}]

+ 42 - 54
src/main/frontend/components/content.cljs

@@ -31,13 +31,22 @@
 (rum/defc custom-context-menu-content
   []
   [:.menu-links-wrapper
+   (ui/menu-background-color #(editor-handler/batch-add-block-property! (state/get-selection-block-ids) :background-color %)
+                             #(editor-handler/batch-remove-block-property! (state/get-selection-block-ids) :background-color))
+
+   (ui/menu-heading #(editor-handler/batch-set-heading! (state/get-selection-block-ids) %)
+                    #(editor-handler/batch-set-heading! (state/get-selection-block-ids) true)
+                    #(editor-handler/batch-remove-heading! (state/get-selection-block-ids)))
+
+   [:hr.menu-separator]
+
    (ui/menu-link
     {:key "cut"
      :on-click #(editor-handler/cut-selection-blocks true)}
     (t :content/cut)
     nil)
    (ui/menu-link
-    {:key      "delete"
+    {:key "delete"
      :on-click #(do (editor-handler/delete-selection %)
                     (state/hide-custom-context-menu!))}
     "Delete"
@@ -68,10 +77,31 @@
 
    [:hr.menu-separator]
 
+   (when (state/enable-flashcards?)
+     (ui/menu-link
+      {:key "Make a Card"
+       :on-click #(srs/batch-make-cards!)}
+      "Make a Flashcard"
+      nil))
+
    (ui/menu-link
     {:key "cycle todos"
      :on-click editor-handler/cycle-todos!}
     "Cycle todos"
+    nil)
+
+   [:hr.menu-separator]
+
+   (ui/menu-link
+    {:key "Expand all"
+     :on-click editor-handler/expand-all-selection!}
+    "Expand all"
+    nil)
+
+   (ui/menu-link
+    {:key "Collapse all"
+     :on-click editor-handler/collapse-all-selection!}
+    "Collapse all"
     nil)])
 
 (defonce *template-including-parent? (atom nil))
@@ -84,7 +114,7 @@
               #(swap! *template-including-parent? not))])
 
 (rum/defcs block-template < rum/reactive
-  (shortcut/disable-all-shortcuts)
+  shortcut/disable-all-shortcuts
   (rum/local false ::edit?)
   (rum/local "" ::input)
   {:will-unmount (fn [state]
@@ -134,61 +164,19 @@
        "Make a Template"
        nil))))
 
-(rum/defc ^:large-vars/cleanup-todo block-context-menu-content
+(rum/defc ^:large-vars/cleanup-todo block-context-menu-content <
+  shortcut/disable-all-shortcuts
   [_target block-id]
     (when-let [block (db/entity [:block/uuid block-id])]
-      (let [format (:block/format block)
-            heading (-> block :block/properties :heading)]
+      (let [heading (-> block :block/properties :heading (or false))]
         [:.menu-links-wrapper
-         [:div.flex.flex-row.justify-between.py-1.px-2.items-center
-          [:div.flex.flex-row.justify-between.flex-1.mx-2.mt-2
-           (for [color ui/block-background-colors]
-             [:a.shadow-sm
-              {:title (t (keyword "color" color))
-               :on-click (fn [_e]
-                           (editor-handler/set-block-property! block-id "background-color" color))}
-              [:div.heading-bg {:style {:background-color (str "var(--color-" color "-500)")}}]])
-           [:a.shadow-sm
-            {:title    (t :remove-background)
-             :on-click (fn [_e]
-                         (editor-handler/remove-block-property! block-id "background-color"))}
-            [:div.heading-bg.remove "-"]]]]
-
-         [:div.flex.flex-row.justify-between.pb-2.pt-1.px-2.items-center
-          [:div.flex.flex-row.justify-between.flex-1.px-1
-           (for [i (range 1 7)]
-             (ui/button
-              ""
-              :disabled (= heading i)
-              :icon (str "h-" i)
-              :title (t :heading i)
-              :class "to-heading-button"
-              :on-click (fn [_e]
-                          (editor-handler/set-heading! block-id format i))
-              :intent "link"
-              :small? true))
-           (ui/button
-            ""
-            :icon "h-auto"
-            :disabled (= heading true)
-            :icon-props {:extension? true}
-            :class "to-heading-button"
-            :title (t :auto-heading)
-            :on-click (fn [_e]
-                        (editor-handler/set-heading! block-id format true))
-            :intent "link"
-            :small? true)
-           (ui/button
-            ""
-            :icon "heading-off"
-            :disabled (not heading)
-            :icon-props {:extension? true}
-            :class "to-heading-button"
-            :title (t :remove-heading)
-            :on-click (fn [_e]
-                        (editor-handler/remove-heading! block-id format))
-            :intent "link"
-            :small? true)]]
+         (ui/menu-background-color #(editor-handler/set-block-property! block-id :background-color %)
+                                   #(editor-handler/remove-block-property! block-id :background-color))
+
+         (ui/menu-heading heading
+                          #(editor-handler/set-heading! block-id %)
+                          #(editor-handler/set-heading! block-id true)
+                          #(editor-handler/remove-heading! block-id))
 
          [:hr.menu-separator]
 

+ 1 - 1
src/main/frontend/components/search.cljs

@@ -480,7 +480,7 @@
     (t :search)))
 
 (rum/defcs search-modal < rum/reactive
-  (shortcut/disable-all-shortcuts)
+  shortcut/disable-all-shortcuts
   (mixins/event-mixin
    (fn [state]
      (mixins/hide-when-esc-or-outside

+ 1 - 1
src/main/frontend/components/select.cljs

@@ -32,7 +32,7 @@
         [:code.opacity-20.bg-transparent (:id result)]])]))
 
 (rum/defcs select < rum/reactive
-  (shortcut/disable-all-shortcuts)
+  shortcut/disable-all-shortcuts
   (rum/local "" ::input)
   {:init (fn [state]
            (assoc state ::selected-choices

+ 1 - 1
src/main/frontend/components/user/login.cljs

@@ -73,7 +73,7 @@
              (user-pane sign-out! user')))))]))
 
 (rum/defcs page <
-  (shortcut/disable-all-shortcuts)
+  shortcut/disable-all-shortcuts
   [_state]
   (page-impl))
 

+ 16 - 0
src/main/frontend/extensions/srs.cljs

@@ -779,6 +779,22 @@
          block-id
          (str (string/trim content) " #" card-hash-tag))))))
 
+(defn batch-make-cards!
+  ([] (batch-make-cards! (state/get-selection-block-ids)))
+  ([block-ids]
+   (let [block-content-fn (fn [block]
+                            [block (-> (property/remove-built-in-properties (:block/format block) (:block/content block))
+                                       (drawer/remove-logbook)
+                                       string/trim
+                                       (str " #" card-hash-tag))])
+         blocks (->> block-ids
+                     (map #(db/entity [:block/uuid %]))
+                     (remove card-block?)
+                     (map #(db/pull [:block/uuid (:block/uuid %)]))
+                     (map block-content-fn))]
+     (when-not (empty? blocks)
+       (editor-handler/save-blocks! blocks)))))
+
 (defonce *due-cards-interval (atom nil))
 
 (defn update-cards-due-count!

+ 121 - 21
src/main/frontend/handler/editor.cljs

@@ -852,6 +852,26 @@
                             (dom/attr sibling-block "id")
                             "")))))
 
+(defn- set-block-property-aux!
+  [block-or-id key value]
+  (when-let [block (cond (string? block-or-id) (db/entity [:block/uuid (uuid block-or-id)])
+                         (uuid? block-or-id) (db/entity [:block/uuid block-or-id])
+                         :else block-or-id)]
+    (let [format (:block/format block)
+          content (:block/content block)
+          properties (:block/properties block)
+          properties (if (nil? value)
+                       (dissoc properties key)
+                       (assoc properties key value))
+          content (if (nil? value)
+                    (property/remove-property format key content)
+                    (property/insert-property format content key value))
+          content (property/remove-empty-properties content)]
+      {:block/uuid (:block/uuid block)
+       :block/properties properties
+       :block/properties-order (or (keys properties) {})
+       :block/content content})))
+
 (defn- batch-set-block-property!
   "col: a collection of [block-id property-key property-value]."
   [col]
@@ -899,6 +919,14 @@
                        input-pos
                        (state/get-edit-input-id)))))))
 
+(defn batch-add-block-property!
+  [block-ids property-key property-value]
+  (batch-set-block-property! (map #(vector % property-key property-value) block-ids)))
+
+(defn batch-remove-block-property!
+  [block-ids property-key]
+  (batch-set-block-property! (map #(vector % property-key nil) block-ids)))
+
 (defn remove-block-property!
   [block-id key]
   (let [key (keyword key)]
@@ -1291,14 +1319,20 @@
   ([repo block-or-uuid content]
    (let [block (if (or (uuid? block-or-uuid)
                        (string? block-or-uuid))
-                 (db-model/query-block-by-uuid block-or-uuid) block-or-uuid)
-         format (:block/format block)]
-     (save-block! {:block block :repo repo :format format} content)))
+                 (db-model/query-block-by-uuid block-or-uuid) block-or-uuid)]
+     (save-block! {:block block :repo repo} content)))
   ([{:keys [block repo] :as _state} value]
    (let [repo (or repo (state/get-current-repo))]
      (when (db/entity repo [:block/uuid (:block/uuid block)])
        (save-block-aux! block value {})))))
 
+(defn save-blocks!
+  [blocks]
+  (outliner-tx/transact!
+   {:outliner-op :save-block}
+    (doseq [[block value] blocks]
+      (save-block-if-changed! block value))))
+
 (defn save-current-block!
   "skip-properties? if set true, when editing block is likely be properties, skip saving"
   ([]
@@ -3494,6 +3528,28 @@
          block-ids (map :block/uuid blocks)]
      (set-blocks-collapsed! block-ids false))))
 
+(defn collapse-all-selection!
+  []
+  (let [block-ids (->> (get-selected-toplevel-block-uuids)
+                       (map #(all-blocks-with-level {:incremental? false
+                                                     :expanded? true
+                                                     :root-block %}))
+                       flatten
+                       (map :block/uuid)
+                       distinct)]
+    (set-blocks-collapsed! block-ids true)))
+
+(defn expand-all-selection!
+  []
+  (let [block-ids (->> (get-selected-toplevel-block-uuids)
+                       (map #(all-blocks-with-level {:incremental? false
+                                                     :collapse? true
+                                                     :root-block %}))
+                       flatten
+                       (map :block/uuid)
+                       distinct)]
+    (set-blocks-collapsed! block-ids false)))
+
 (defn toggle-open! []
   (let [all-expanded? (empty? (all-blocks-with-level {:incremental? false
                                                       :collapse? true}))]
@@ -3643,26 +3699,70 @@
     (first (:block/_parent (db/entity (:db/id block)))))
    (util/collapsed? block)))
 
-(defn remove-heading!
-  [block-id format]
-  (remove-block-property! block-id "heading")
-  (when (= format :markdown)
-    (let [repo (state/get-current-repo)
-          block (db/entity [:block/uuid block-id])
-          content' (commands/clear-markdown-heading (:block/content block))]
-      (save-block! repo block-id content'))))
+(defn- set-heading-aux!
+  [block-id heading]
+  (let [block (db/pull [:block/uuid block-id])
+        format (:block/format block)
+        old-heading (get-in block [:block/properties :heading])]
+    (if (= format :markdown)
+      (cond
+        ;; nothing changed
+        (or (and (nil? old-heading) (nil? heading))
+            (and (true? old-heading) (true? heading))
+            (= old-heading heading))
+        nil
+
+        (or (and (nil? old-heading) (true? heading))
+            (and (true? old-heading) (nil? heading)))
+        (set-block-property-aux! block :heading heading)
+
+        (and (or (nil? heading) (true? heading))
+             (number? old-heading))
+        (let [block' (set-block-property-aux! block :heading heading)
+              content (commands/clear-markdown-heading (:block/content block'))]
+          (merge block' {:block/content content}))
+
+        (and (or (nil? old-heading) (true? old-heading))
+             (number? heading))
+        (let [block' (set-block-property-aux! block :heading nil)
+              properties (assoc (:block/properties block) :heading heading)
+              content (commands/set-markdown-heading (:block/content block') heading)]
+          (merge block' {:block/content content :block/properties properties}))
+
+        ;; heading-num1 -> heading-num2
+        :else
+        (let [properties (assoc (:block/properties block) :heading heading)
+              content (-> block
+                          :block/content
+                          commands/clear-markdown-heading
+                          (commands/set-markdown-heading heading))]
+          {:block/uuid (:block/uuid block)
+           :block/properties properties
+           :block/content content}))
+      (set-block-property-aux! block :heading heading))))
 
 (defn set-heading!
-  [block-id format heading]
-  (remove-heading! block-id format)
-  (if (or (true? heading) (not= format :markdown))
-    (do
-      (save-current-block!)
-      (set-block-property! block-id "heading" heading))
-    (let [repo (state/get-current-repo)
-          block (db/entity [:block/uuid block-id])
-          content' (commands/set-markdown-heading (:block/content block) heading)]
-      (save-block! repo block-id content'))))
+  [block-id heading]
+  (when-let [block (set-heading-aux! block-id heading)]
+    (outliner-tx/transact!
+     {:outliner-op :save-block}
+     (outliner-core/save-block! block))))
+
+(defn remove-heading!
+  [block-id]
+  (set-heading! block-id nil))
+
+(defn batch-set-heading!
+  [block-ids heading]
+  (outliner-tx/transact!
+   {:outliner-op :save-block}
+   (doseq [block-id block-ids]
+     (when-let [block (set-heading-aux! block-id heading)]
+       (outliner-core/save-block! block)))))
+
+(defn batch-remove-heading!
+  [block-ids]
+  (batch-set-heading! block-ids nil))
 
 (defn block->data-transfer!
   "Set block or page name to the given event's dataTransfer. Used in dnd."

+ 1 - 1
src/main/frontend/handler/events.cljs

@@ -732,7 +732,7 @@
 
 (defmethod handle :editor/set-org-mode-heading [[_ block heading]]
   (when-let [id (:block/uuid block)]
-    (editor-handler/set-heading! id :org heading)))
+    (editor-handler/set-heading! id heading)))
 
 (defmethod handle :file-sync-graph/restore-file [[_ graph page-entity content]]
   (when (db/get-db graph)

+ 2 - 2
src/main/frontend/modules/outliner/core.cljs

@@ -823,14 +823,14 @@
 
 (def ^:private ^:dynamic *transaction-data*
   "Stores transaction-data that are generated by one or more write-operations,
-  see also `frontend.modules.outliner.transaction/save-transactions`"
+  see also `frontend.modules.outliner.transaction/transact!`"
   nil)
 
 (defn- op-transact!
   [fn-var & args]
   {:pre [(var? fn-var)]}
   (when (nil? *transaction-data*)
-    (throw (js/Error. (str (:name (meta fn-var)) " is not used in (save-transactions ...)"))))
+    (throw (js/Error. (str (:name (meta fn-var)) " is not used in (transact! ...)"))))
   (let [result (apply @fn-var args)]
     (conj! *transaction-data* (select-keys result [:tx-data :tx-meta]))
     result))

+ 1 - 1
src/main/frontend/modules/shortcut/core.cljs

@@ -165,7 +165,7 @@
           :when (not= group :shortcut.handler/misc)]
     (events/listen handler EventType/SHORTCUT_TRIGGERED dispatch-fn)))
 
-(defn disable-all-shortcuts []
+(def disable-all-shortcuts
   {:will-mount
    (fn [state]
      (unlisten-all)

+ 52 - 1
src/main/frontend/ui.cljs

@@ -64,6 +64,20 @@
    "purple"
    "gray"])
 
+(rum/defc menu-background-color
+  [add-bgcolor-fn rm-bgcolor-fn]
+  [:div.flex.flex-row.justify-between.py-1.px-2.items-center
+   [:div.flex.flex-row.justify-between.flex-1.mx-2.mt-2
+    (for [color block-background-colors]
+      [:a.shadow-sm
+       {:title (t (keyword "color" color))
+        :on-click #(add-bgcolor-fn color)}
+       [:div.heading-bg {:style {:background-color (str "var(--color-" color "-500)")}}]])
+    [:a.shadow-sm
+     {:title (t :remove-background)
+      :on-click rm-bgcolor-fn}
+     [:div.heading-bg.remove "-"]]]])
+
 (rum/defc ls-textarea
   < rum/reactive
   {:did-mount (fn [state]
@@ -986,7 +1000,7 @@
        :title title
        :disabled disabled?
        :class (str (util/hiccup->class klass) " " class)}
-      (dissoc option :background :class :small? :large?)
+      (dissoc option :background :class :small? :large? :disabled?)
       (when href
         {:on-click (fn []
                      (util/open-url href)
@@ -1094,3 +1108,40 @@
       [])
      (when portal-anchor
        (rum/portal (rum/fragment children) portal-anchor)))))
+
+(rum/defc menu-heading
+  ([add-heading-fn auto-heading-fn rm-heading-fn]
+   (menu-heading nil add-heading-fn auto-heading-fn rm-heading-fn))
+  ([heading add-heading-fn auto-heading-fn rm-heading-fn]
+   [:div.flex.flex-row.justify-between.pb-2.pt-1.px-2.items-center
+    [:div.flex.flex-row.justify-between.flex-1.px-1
+     (for [i (range 1 7)]
+       (button
+        ""
+        :disabled? (and (some? heading) (= heading i))
+        :icon (str "h-" i)
+        :title (t :heading i)
+        :class "to-heading-button"
+        :on-click #(add-heading-fn i)
+        :intent "link"
+        :small? true))
+     (button
+      ""
+      :icon "h-auto"
+      :disabled? (and (some? heading) (true? heading))
+      :icon-props {:extension? true}
+      :class "to-heading-button"
+      :title (t :auto-heading)
+      :on-click auto-heading-fn
+      :intent "link"
+      :small? true)
+     (button
+      ""
+      :icon "heading-off"
+      :disabled? (and (some? heading) (not heading))
+      :icon-props {:extension? true}
+      :class "to-heading-button"
+      :title (t :remove-heading)
+      :on-click rm-heading-fn
+      :intent "link"
+      :small? true)]]))

+ 1 - 2
src/main/logseq/api.cljs

@@ -525,8 +525,7 @@
 
 (def ^:export get_selected_blocks
   (fn []
-    (when-let [blocks (and (state/in-selection-mode?)
-                           (seq (state/get-selection-blocks)))]
+    (when-let [blocks (state/selection?)]
       (let [blocks (->> blocks
                         (map (fn [^js el] (some-> (.getAttribute el "blockid")
                                                   (db-model/query-block-by-uuid)))))]