diff --git a/src/main/frontend/components/command_palette.cljs b/src/main/frontend/components/command_palette.cljs index 8516fe1f05..318a5b17d8 100644 --- a/src/main/frontend/components/command_palette.cljs +++ b/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}}] diff --git a/src/main/frontend/components/content.cljs b/src/main/frontend/components/content.cljs index 8771e8b553..47d959d795 100644 --- a/src/main/frontend/components/content.cljs +++ b/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 "-"]]]] + (ui/menu-background-color #(editor-handler/set-block-property! block-id :background-color %) + #(editor-handler/remove-block-property! block-id :background-color)) - [: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-heading heading + #(editor-handler/set-heading! block-id %) + #(editor-handler/set-heading! block-id true) + #(editor-handler/remove-heading! block-id)) [:hr.menu-separator] diff --git a/src/main/frontend/components/search.cljs b/src/main/frontend/components/search.cljs index 1cba6e6fe4..39733bd44d 100644 --- a/src/main/frontend/components/search.cljs +++ b/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 diff --git a/src/main/frontend/components/select.cljs b/src/main/frontend/components/select.cljs index bee8797fac..325ba13694 100644 --- a/src/main/frontend/components/select.cljs +++ b/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 diff --git a/src/main/frontend/components/user/login.cljs b/src/main/frontend/components/user/login.cljs index d471b7947e..d9062c3332 100644 --- a/src/main/frontend/components/user/login.cljs +++ b/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)) diff --git a/src/main/frontend/extensions/srs.cljs b/src/main/frontend/extensions/srs.cljs index 2ba4739046..1cc17a1289 100644 --- a/src/main/frontend/extensions/srs.cljs +++ b/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! diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index b8d9f6f50d..d1154873fe 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/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." diff --git a/src/main/frontend/handler/events.cljs b/src/main/frontend/handler/events.cljs index 1930c2dd72..28761cb6ba 100644 --- a/src/main/frontend/handler/events.cljs +++ b/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) diff --git a/src/main/frontend/modules/outliner/core.cljs b/src/main/frontend/modules/outliner/core.cljs index 221c87ddb0..0084dbc0fc 100644 --- a/src/main/frontend/modules/outliner/core.cljs +++ b/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)) diff --git a/src/main/frontend/modules/shortcut/core.cljs b/src/main/frontend/modules/shortcut/core.cljs index 5d8e2780a0..d45f1fc2d2 100644 --- a/src/main/frontend/modules/shortcut/core.cljs +++ b/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) diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs index f7d3a9d704..f823946b96 100644 --- a/src/main/frontend/ui.cljs +++ b/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)]])) diff --git a/src/main/logseq/api.cljs b/src/main/logseq/api.cljs index 445ae532ad..04ecfbc0b0 100644 --- a/src/main/logseq/api.cljs +++ b/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)))))]