Browse Source

Merge pull request #11555 from logseq/enhance/code-type-block

Enhance(ux): code type block
Tienson Qin 1 year ago
parent
commit
eeecc7ff07

+ 1 - 1
deps/db/src/logseq/db/frontend/property.cljs

@@ -43,7 +43,7 @@
                                                 :public? false
                                                 :hide? true
                                                 :view-context :block}}
-   :logseq.property.code/mode {:title "Code mode"
+   :logseq.property.code/lang {:title "Code mode"
                                :schema {:type :string
                                         :public? false
                                         :hide? true

+ 1 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -2,7 +2,7 @@
   "Main datascript schemas for the Logseq app"
   (:require [clojure.set :as set]))
 
-(def version 28)
+(def version 29)
 ;; A page is a special block, a page can corresponds to multiple files with the same ":block/name".
 (def ^:large-vars/data-var schema
   {:db/ident        {:db/unique :db.unique/identity}

+ 2 - 2
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -644,7 +644,7 @@
                                        options))
         pvalues-tx' (if (and pvalues-tx (seq advanced-query))
                       (concat pvalues-tx [{:block/uuid (second (:logseq.property/query block-properties))
-                                           :logseq.property.code/mode "clojure"
+                                           :logseq.property.code/lang "clojure"
                                            :logseq.property.node/display-type :code}])
                       pvalues-tx)]
     {:block
@@ -1295,4 +1295,4 @@
        (export-favorites-from-config-edn conn repo-or-conn config {})
        (export-class-properties conn repo-or-conn)
        {:import-state (:import-state doc-options)
-        :files files}))))
+        :files files}))))

+ 1 - 1
package.json

@@ -125,7 +125,7 @@
         "check-password-strength": "2.0.7",
         "chokidar": "3.5.1",
         "chrono-node": "2.2.4",
-        "codemirror": "5.65.13",
+        "codemirror": "5.65.18",
         "comlink": "^4.4.1",
         "d3-force": "3.0.0",
         "diff": "5.0.0",

+ 1 - 1
resources/css/codemirror.lsradix.css

@@ -36,7 +36,7 @@ http://ethanschoonover.com/lsradix/img/lsradix-palette.png
 }
 .cm-s-lsradix.cm-s-dark {
   background-color: var(--lx-gray-01, hsl(var(--secondary)/.7));
-  color: var(--lx-gray-10, hsl(var(--secondary-foreground)));
+  color: var(--ls-primary-text-color, var(--lx-gray-10, hsl(var(--secondary-foreground))));
 }
 
 .dark .cm-s-lsradix.cm-s-dark {

+ 8 - 3
src/main/frontend/commands.cljs

@@ -188,14 +188,14 @@
      [:editor/set-property :block/tags :logseq.class/Query]
      [:editor/set-property :logseq.property/query ""]
      [:editor/set-property-on-block-property :logseq.property/query :logseq.property.node/display-type :code]
-     [:editor/set-property-on-block-property :logseq.property/query :logseq.property.code/mode "clojure"]]
+     [:editor/set-property-on-block-property :logseq.property/query :logseq.property.code/lang "clojure"]]
     (->block "query")))
 
 (defn db-based-code-block
   []
   [[:editor/input "" {:last-pattern command-trigger}]
-   [:editor/set-property :logseq.property.node/display-type :code]
-   [:codemirror/focus]])
+   [:editor/upsert-type-block :code]
+   [:editor/exit]])
 
 (defn file-based-code-block
   []
@@ -752,6 +752,11 @@
       (when block-property-value
         (db-property-handler/set-block-property! (:db/id block-property-value) property-id value)))))
 
+(defmethod handle-step :editor/upsert-type-block [[_ type]]
+  (when (config/db-based-graph? (state/get-current-repo))
+    (when-let [block (state/get-edit-block)]
+      (state/pub-event! [:editor/upsert-type-block {:block block :type type}]))))
+
 (defn- file-based-set-priority
   [priority]
   (when-let [input-id (state/get-edit-input-id)]

+ 104 - 36
src/main/frontend/components/block.cljs

@@ -23,6 +23,7 @@
             [frontend.components.query.builder :as query-builder-component]
             [frontend.components.svg :as svg]
             [frontend.components.title :as title]
+            [frontend.components.select :as select]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
@@ -91,7 +92,8 @@
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
-            [shadow.loader :as loader]))
+            [shadow.loader :as loader]
+            [frontend.storage :as storage]))
 
 ;; local state
 (defonce *dragging?
@@ -2152,7 +2154,7 @@
     (cond
       (= :code node-type)
       [:div.flex.flex-1.w-full
-       (src-cp (assoc config :block block) {:language (:logseq.property.code/mode block)})]
+       (src-cp (assoc config :block block) {:language (:logseq.property.code/lang block)})]
 
       ;; TODO: switched to https://cortexjs.io/mathlive/ for editing
       (= :math node-type)
@@ -2401,7 +2403,8 @@
                           content
                           block
                           cursor-range
-                          {:move-cursor? false
+                          {:db (db/get-db)
+                           :move-cursor? false
                            :container-id (:container-id config)}))]
                 ;; wait a while for the value of the caret range
                 (p/do!
@@ -2749,14 +2752,17 @@
         refs-count (if (seq (:block/_refs block))
                      (count (:block/_refs block))
                      (rum/react *refs-count))
-        table? (:table? config)]
+        table? (:table? config)
+        type-block-editor? (and (contains? #{:code} (:logseq.property.node/display-type block))
+                                (not= (:db/id block) (:db/id (state/sub :editor/raw-mode-block))))
+        config (assoc config :block-parent-id block-id)]
     [:div.block-content-or-editor-wrap
      {:class (when (:page-title? config) "ls-page-title-container")
       :data-node-type (some-> (:logseq.property.node/display-type block) name)}
      (when (and db-based? (not table?)) (block-positioned-properties config block :block-left))
      [:div.block-content-or-editor-inner
       [:div.flex.flex-1.flex-row.gap-1.items-center
-       (if (and edit? editor-box)
+       (if (and edit? editor-box (not type-block-editor?))
          [:div.editor-wrapper.flex.flex-1
           {:id editor-id
            :class (util/classnames [{:opacity-50 (boolean (or (ldb/built-in? block) (ldb/journal? block)))}])}
@@ -2777,7 +2783,8 @@
                                                           (:block/title block))]
                                           (editor-handler/clear-selection!)
                                           (editor-handler/unhighlight-blocks!)
-                                          (state/set-editing! edit-input-id content block "" {:container-id (:container-id config)}))}})
+                                          (state/set-editing! edit-input-id content block "" {:db (db/get-db)
+                                                                                              :container-id (:container-id config)}))}})
            (block-content config block edit-input-id block-id slide?))
 
           (when (and db-based? (not table?)) (block-positioned-properties config block :block-right))
@@ -3579,39 +3586,100 @@
 
 (declare ->hiccup)
 
+(defn- get-code-mode-by-lang
+  [lang]
+  (some (fn [m] (when (= (.-name m) lang) (.-mode m))) js/window.CodeMirror.modeInfo))
+
+(rum/defc src-lang-picker
+  [block on-select!]
+  (when-let [langs (map (fn [m] (.-name m)) js/window.CodeMirror.modeInfo)]
+    (let [options (map (fn [lang] {:label lang :value lang}) langs)]
+      (select/select {:items options
+                      :input-default-placeholder "Choose language"
+                      :on-chosen
+                      (fn [chosen _ _ e]
+                        (let [lang (:value chosen)]
+                          (when (and (= :code (:logseq.property.node/display-type block))
+                                     (not= lang (:logseq.property.code/lang block)))
+                            (on-select! lang e)))
+                        (shui/popup-hide!))}))))
+
 (rum/defc src-cp < rum/static
   [config options]
-  (when options
-    (let [html-export? (:html-export? config)
-          {:keys [lines language]} options
-          attr (when language
-                 {:data-lang language})
-          code (if lines (apply str lines) (:block/title (:block config)))
-          [inside-portal? set-inside-portal?] (rum/use-state nil)]
-      (cond
-        html-export?
-        (highlight/html-export attr code)
+  (let [block (:block config)
+        container-id (:container-id config)
+        *mode-ref (rum/use-ref nil)
+        *actions-ref (rum/use-ref nil)]
+
+    (when options
+      (let [html-export? (:html-export? config)
+            {:keys [lines language]} options
+            attr (when language
+                   {:data-lang language})
+            code (if lines (apply str lines) (:block/title block))
+            [inside-portal? set-inside-portal?] (rum/use-state nil)]
+        (cond
+          html-export?
+          (highlight/html-export attr code)
 
-        :else
-        (let [language (if (contains? #{"edn" "clj" "cljc" "cljs"} language) "clojure" language)]
-          [:div.ui-fenced-code-editor.flex.flex-1
-           {:ref (fn [el]
-                   (set-inside-portal? (and el (whiteboard-handler/inside-portal? el))))}
-           (cond
-             (nil? inside-portal?) nil
-
-             (or (:slide? config) inside-portal?)
-             (highlight/highlight (str (random-uuid))
-                                  {:class     (str "language-" language)
-                                   :data-lang language}
-                                  code)
-
-             :else
-             [:<>
-              (lazy-editor/editor config (str (d/squuid)) attr code options)
-              (let [options (:options options) block (:block config)]
-                (when (and (= language "clojure") (contains? (set options) ":results"))
-                  (sci/eval-result code block)))])])))))
+          :else
+          (let [language (if (contains? #{"edn" "clj" "cljc" "cljs"} language) "clojure" language)]
+            [:div.ui-fenced-code-editor.flex.flex-1
+             {:ref (fn [el]
+                     (set-inside-portal? (and el (whiteboard-handler/inside-portal? el))))
+              :on-mouse-over #(dom/add-class! (rum/deref *actions-ref) "opacity-100")
+              :on-mouse-leave (fn [e]
+                                (when (dom/has-class? (.-target e) "code-editor")
+                                  (dom/remove-class! (rum/deref *actions-ref) "opacity-100")))}
+             (cond
+               (nil? inside-portal?) nil
+
+               (or (:slide? config) inside-portal?)
+               (highlight/highlight (str (random-uuid))
+                                    {:class (str "language-" language)
+                                     :data-lang language}
+                                    code)
+
+               :else
+               [:div.ls-code-editor-wrap
+                [:div.code-block-actions.flex.flex-row.gap-1.opacity-0.transition-opacity.ease-in.duration-300
+                 {:ref *actions-ref}
+                 (shui/button
+                  {:variant :text
+                   :size :sm
+                   :class "select-language"
+                   :ref *mode-ref
+                   :containerid (str container-id)
+                   :blockid (str (:block/uuid block))
+                   :on-click (fn [^js e]
+                               (util/stop-propagation e)
+                               (let [target (.-target e)]
+                                 (shui/popup-show! target
+                                                   #(src-lang-picker block
+                                                                     (fn [lang ^js _e]
+                                                                       (when-let [^js cm (util/get-cm-instance (util/rec-get-node target "ls-block"))]
+                                                                         (if-let [mode (get-code-mode-by-lang lang)]
+                                                                           (.setOption cm "mode" mode)
+                                                                           (throw (ex-info "code mode not found"
+                                                                                           {:lang lang})))
+                                                                         (storage/set :latest-code-lang lang)
+                                                                         (db-property-handler/set-block-property!
+                                                                          (:db/id block) :logseq.property.code/lang lang))))
+                                                   {:align :end})))}
+                  (or language "Choose language")
+                  (ui/icon "chevron-down"))
+                 (shui/button
+                  {:variant :text
+                   :size :sm
+                   :on-click (fn [e]
+                               (util/stop-propagation e)
+                               (editor-handler/copy-block-content! block))}
+                  (ui/icon "copy")
+                  "Copy")]
+                (lazy-editor/editor config (str (d/squuid)) attr code options)
+                (let [options (:options options) block (:block config)]
+                  (when (and (= language "clojure") (contains? (set options) ":results"))
+                    (sci/eval-result code block)))])]))))))
 
 (defn ^:large-vars/cleanup-todo markup-element-cp
   [{:keys [html-export?] :as config} item]

+ 19 - 0
src/main/frontend/components/block.css

@@ -943,3 +943,22 @@ html.is-mac {
 .ls-page-title .block-tags {
   @apply relative -right-1 min-h-full;
 }
+
+.ls-code-editor-wrap {
+  @apply relative w-full;
+
+  .extensions__code-lang {
+    @apply hidden;
+  }
+
+  > .code-block-actions {
+    @apply absolute right-1 top-1 select-none z-[1] text-xs;
+    button {
+        @apply !py-0 h-4 text-muted-foreground hover:text-foreground text-xs px-1;
+    }
+    svg {
+      width: 14px;
+      height: 14px;
+    }
+  }
+}

+ 80 - 80
src/main/frontend/components/container.cljs

@@ -168,7 +168,7 @@
   (let [_favorites-updated? (state/sub :favorites/updated?)
         favorite-entities (page-handler/get-favorites)]
     (nav-content-item
-      [:a.flex.items-center.text-sm.font-medium.rounded-md.wrap-th
+     [:a.flex.items-center.text-sm.font-medium.rounded-md.wrap-th
       (ui/icon "star" {:size 16})
       [:strong.flex-1.ml-2 (string/upper-case (t :left-side-bar/nav-favorites))]]
 
@@ -409,32 +409,32 @@
 
     ;; restore size
     (rum/use-layout-effect!
-      (fn []
-        (when-let [width (storage/get :ls-left-sidebar-width)]
-          (.setProperty (.-style el-doc) "--ls-left-sidebar-width" width)))
-      [])
+     (fn []
+       (when-let [width (storage/get :ls-left-sidebar-width)]
+         (.setProperty (.-style el-doc) "--ls-left-sidebar-width" width)))
+     [])
 
     ;; draggable handler
     (rum/use-effect!
-      (fn []
-        (when-let [el (and (fn? js/window.interact) (rum/deref *el-ref))]
-          (let [^js sidebar-el (.querySelector el-doc "#left-sidebar")]
-            (-> (js/interact el)
-              (.draggable
+     (fn []
+       (when-let [el (and (fn? js/window.interact) (rum/deref *el-ref))]
+         (let [^js sidebar-el (.querySelector el-doc "#left-sidebar")]
+           (-> (js/interact el)
+               (.draggable
                 #js {:listeners
                      #js {:move (fn [^js/MouseEvent e]
                                   (when-let [offset (.-left (.-rect e))]
                                     (let [width (.toFixed (max (min offset 460) 240) 2)]
                                       (adjust-size! (str width "px")))))}})
-              (.styleCursor false)
-              (.on "dragstart" (fn []
-                                 (.. sidebar-el -classList (add "is-resizing"))
-                                 (.. el-doc -classList (add "is-resizing-buf"))))
-              (.on "dragend" (fn []
-                               (.. sidebar-el -classList (remove "is-resizing"))
-                               (.. el-doc -classList (remove "is-resizing-buf"))))))
-          #()))
-      [])
+               (.styleCursor false)
+               (.on "dragstart" (fn []
+                                  (.. sidebar-el -classList (add "is-resizing"))
+                                  (.. el-doc -classList (add "is-resizing-buf"))))
+               (.on "dragend" (fn []
+                                (.. sidebar-el -classList (remove "is-resizing"))
+                                (.. el-doc -classList (remove "is-resizing-buf"))))))
+         #()))
+     [])
     [:span.left-sidebar-resizer {:ref *el-ref}]))
 
 (rum/defcs left-sidebar < rum/reactive
@@ -479,7 +479,7 @@
 
      ;; sidebar contents
      (sidebar-nav route-match close-fn left-sidebar-open? enable-whiteboards? srs-open? *closing?
-       @*close-signal (and touch-pending? touching-x-offset))
+                  @*close-signal (and touch-pending? touching-x-offset))
      ;; resizer
      (sidebar-resizer)]))
 
@@ -724,16 +724,16 @@
   []
 
   (rum/use-effect!
-    (fn []
-      (state/set-state! :ui/handbooks-open? false))
-    [])
+   (fn []
+     (state/set-state! :ui/handbooks-open? false))
+   [])
 
   (rum/use-effect!
-    (fn []
-      (let [h #(state/set-state! :ui/help-open? false)]
-        (.addEventListener js/document.body "click" h)
-        #(.removeEventListener js/document.body "click" h)))
-    [])
+   (fn []
+     (let [h #(state/set-state! :ui/help-open? false)]
+       (.addEventListener js/document.body "click" h)
+       #(.removeEventListener js/document.body "click" h)))
+   [])
 
   [:div.cp__sidebar-help-menu-popup
    [:div.list-wrap
@@ -777,58 +777,58 @@
 
 (rum/defc app-context-menu-observer
   < rum/static
-    (mixins/event-mixin
-      (fn [state]
+  (mixins/event-mixin
+   (fn [state]
         ;; fixme: this mixin will register global event listeners on window
         ;; which might cause unexpected issues
-        (mixins/listen state js/window "contextmenu"
-          (fn [^js e]
-            (let [target (gobj/get e "target")
-                  block-el (.closest target ".bullet-container[blockid]")
-                  block-id (some-> block-el (.getAttribute "blockid"))
-                  {:keys [block block-ref]} (state/sub :block-ref/context)
-                  {:keys [page page-entity]} (state/sub :page-title/context)]
-
-              (let [show!
-                    (fn [content]
-                      (shui/popup-show! e
-                                        (fn [{:keys [id]}]
-                                          [:div {:on-click #(shui/popup-hide! id)
-                                                 :data-keep-selection true}
-                                           content])
-                                        {:on-before-hide state/dom-clear-selection!
-                                         :on-after-hide state/state-clear-selection!
-                                         :content-props {:class "w-[280px] ls-context-menu-content"}
-                                         :as-dropdown? true}))
-
-                    handled
-                    (cond
-                      (and page (not block-id))
-                      (do
-                        (show! (cp-content/page-title-custom-context-menu-content page-entity))
-                        (state/set-state! :page-title/context nil))
-
-                      block-ref
-                      (do
-                        (show! (cp-content/block-ref-custom-context-menu-content block block-ref))
-                        (state/set-state! :block-ref/context nil))
+     (mixins/listen state js/window "contextmenu"
+                    (fn [^js e]
+                      (let [target (gobj/get e "target")
+                            block-el (.closest target ".bullet-container[blockid]")
+                            block-id (some-> block-el (.getAttribute "blockid"))
+                            {:keys [block block-ref]} (state/sub :block-ref/context)
+                            {:keys [page page-entity]} (state/sub :page-title/context)]
+
+                        (let [show!
+                              (fn [content]
+                                (shui/popup-show! e
+                                                  (fn [{:keys [id]}]
+                                                    [:div {:on-click #(shui/popup-hide! id)
+                                                           :data-keep-selection true}
+                                                     content])
+                                                  {:on-before-hide state/dom-clear-selection!
+                                                   :on-after-hide state/state-clear-selection!
+                                                   :content-props {:class "w-[280px] ls-context-menu-content"}
+                                                   :as-dropdown? true}))
+
+                              handled
+                              (cond
+                                (and page (not block-id))
+                                (do
+                                  (show! (cp-content/page-title-custom-context-menu-content page-entity))
+                                  (state/set-state! :page-title/context nil))
+
+                                block-ref
+                                (do
+                                  (show! (cp-content/block-ref-custom-context-menu-content block block-ref))
+                                  (state/set-state! :block-ref/context nil))
 
                       ;; block selection
-                      (and (state/selection?) (not (d/has-class? target "bullet")))
-                      (show! (cp-content/custom-context-menu-content))
+                                (and (state/selection?) (not (d/has-class? target "bullet")))
+                                (show! (cp-content/custom-context-menu-content))
 
                       ;; block bullet
-                      (and block-id (parse-uuid block-id))
-                      (let [block (.closest target ".ls-block")]
-                        (when block
-                          (state/clear-selection!)
-                          (state/conj-selection-block! block :down))
-                        (show! (cp-content/block-context-menu-content target (uuid block-id))))
-
-                      :else
-                      false)]
-                (when (not (false? handled))
-                  (util/stop e))))))))
+                                (and block-id (parse-uuid block-id))
+                                (let [block (.closest target ".ls-block")]
+                                  (when block
+                                    (state/clear-selection!)
+                                    (state/conj-selection-block! block :down))
+                                  (show! (cp-content/block-context-menu-content target (uuid block-id))))
+
+                                :else
+                                false)]
+                          (when (not (false? handled))
+                            (util/stop e))))))))
   []
   nil)
 
@@ -908,12 +908,12 @@
 
      [:main.theme-container-inner#app-container-wrapper
       {:class (util/classnames
-                [{:ls-left-sidebar-open left-sidebar-open?
-                  :ls-right-sidebar-open sidebar-open?
-                  :ls-wide-mode wide-mode?
-                  :ls-window-controls window-controls?
-                  :ls-fold-button-on-right fold-button-on-right?
-                  :ls-hl-colored ls-block-hl-colored?}])
+               [{:ls-left-sidebar-open left-sidebar-open?
+                 :ls-right-sidebar-open sidebar-open?
+                 :ls-wide-mode wide-mode?
+                 :ls-window-controls window-controls?
+                 :ls-fold-button-on-right fold-button-on-right?
+                 :ls-hl-colored ls-block-hl-colored?}])
        :on-pointer-up (fn []
                         (when-let [container (gdom/getElement "app-container-wrapper")]
                           (d/remove-class! container "blocks-selection-mode")

+ 4 - 1
src/main/frontend/components/editor.cljs

@@ -745,7 +745,10 @@
                   ::ref (atom nil)))
    :did-mount (fn [state]
                 (state/set-editor-args! (:rum/args state))
-                state)}
+                state)
+   :will-unmount (fn [state]
+                   (state/set-state! :editor/raw-mode-block nil)
+                   state)}
   (mixins/event-mixin
    (fn [state]
      (mixins/hide-when-esc-or-outside

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

@@ -133,22 +133,22 @@
                              {:item-render       (or item-cp (fn [result chosen?]
                                                                (render-item result chosen? multiple-choices? *selected-choices)))
                               :class             "cp__select-results"
-                              :on-chosen         (fn [raw-chosen]
+                              :on-chosen         (fn [raw-chosen e]
                                                    (reset! input "")
                                                    (let [chosen (extract-chosen-fn raw-chosen)]
                                                      (if multiple-choices?
                                                        (if (selected-choices chosen)
                                                          (do
                                                            (swap! *selected-choices disj chosen)
-                                                           (when on-chosen (on-chosen chosen false @*selected-choices)))
+                                                           (when on-chosen (on-chosen chosen false @*selected-choices e)))
                                                          (do
                                                            (swap! *selected-choices conj chosen)
-                                                           (when on-chosen (on-chosen chosen true @*selected-choices))))
+                                                           (when on-chosen (on-chosen chosen true @*selected-choices e))))
                                                        (do
                                                          (when (and close-modal? (not multiple-choices?))
                                                            (state/close-modal!))
                                                          (when on-chosen
-                                                           (on-chosen chosen true @*selected-choices))))))
+                                                           (on-chosen chosen true @*selected-choices e))))))
                               :empty-placeholder (empty-placeholder t)})]
 
                            (when (and multiple-choices? (fn? on-apply))
@@ -206,7 +206,7 @@
                           [:div.mb-2 (t :select.graph/empty-placeholder-description)]
                           (ui/button
                            (t :select.graph/add-graph)
-                            :href (rfe/href :graphs)
+                           :href (rfe/href :graphs)
                            :on-click state/close-modal!)])}
    :graph-remove
    {:items-fn (fn []
@@ -223,14 +223,14 @@
    {:items-fn (fn []
                 (let [current-repo (state/get-current-repo)]
                   (->> (state/get-repos)
-                      (remove (fn [{:keys [url]}]
+                       (remove (fn [{:keys [url]}]
                                 ;; Can't replace current graph as ui wouldn't reload properly
-                                (or (= url current-repo) (not (config/db-based-graph? url)))))
-                      (map (fn [{:keys [url] :as original-graph}]
-                             {:value (text-util/get-graph-name-from-path url)
-                              :id (config/get-repo-dir url)
-                              :graph url
-                              :original-graph original-graph})))))
+                                 (or (= url current-repo) (not (config/db-based-graph? url)))))
+                       (map (fn [{:keys [url] :as original-graph}]
+                              {:value (text-util/get-graph-name-from-path url)
+                               :id (config/get-repo-dir url)
+                               :graph url
+                               :original-graph original-graph})))))
     :on-chosen #(dev-common-handler/import-chosen-graph (:graph %))}})
 
 (rum/defc select-modal < rum/reactive

+ 3 - 2
src/main/frontend/components/theme.cljs

@@ -143,8 +143,9 @@
      #(storage/set :file-sync/onboarding-state onboarding-state)
      [onboarding-state])
 
-    [:div.theme-container
-     {:on-click on-click}
+    [:div#root-container.theme-container
+     {:on-click on-click
+      :tab-index -1}
      child
 
      (pdf/default-embed-playground)

+ 4 - 0
src/main/frontend/components/theme.css

@@ -151,6 +151,10 @@ main.ls-fold-button-on-right {
   }
 }
 
+#root-container.theme-container {
+  @apply outline-none;
+}
+
 main.theme-container-inner {
   --left-sidebar-bg-color: var(--lx-gray-02, hsl(var(--secondary, var(--rx-gray-03-hsl))));
 }

+ 60 - 11
src/main/frontend/extensions/code.cljs

@@ -1,5 +1,6 @@
 (ns frontend.extensions.code
-  (:require [clojure.string :as string]
+  (:require [cljs-bean.core :as bean]
+            [clojure.string :as string]
             ["codemirror" :as CodeMirror]
             ["codemirror/addon/edit/closebrackets"]
             ["codemirror/addon/edit/matchbrackets"]
@@ -389,15 +390,16 @@
   [config]
   (p/do!
    (code-handler/save-code-editor!)
-   (when-let [block-id (:block/uuid config)]
-     (let [block (db/entity [:block/uuid block-id])]
-       (editor-handler/edit-block! block :max)))))
+   (when-let [block (:block config)]
+     (let [block (db/entity [:block/uuid (:block/uuid block)])]
+       (state/set-state! :editor/raw-mode-block block)
+       (editor-handler/edit-block! block :max {:save-code-editor? false})))))
 
 (defn ^:large-vars/cleanup-todo render!
   [state]
   (let [[config id attr _code theme user-options] (:rum/args state)
+        edit-block (:block config)
         config-file? (= (:file-path config) "logseq/config.edn")
-        edit-block (state/get-edit-block)
         default-open? (and (:editor/code-mode? @state/state)
                            (= (:block/uuid edit-block)
                               (get-in config [:block :block/uuid])))
@@ -443,7 +445,17 @@
             (reset! *editor-ref editor))]
     (when editor
       (let [textarea-ref (rum/ref-node state textarea-ref-name)
-            element (.getWrapperElement editor)]
+            element (.getWrapperElement editor)
+            *cursor-prev (volatile! nil)
+            *cursor-curr (volatile! nil)
+            update-cursor-state! (fn []
+                                   (let [pos (.getCursor editor)
+                                         pos (bean/->clj (js/JSON.parse (js/JSON.stringify pos)))
+                                         pos (select-keys pos [:line :ch])]
+                                     (if (not @*cursor-prev)
+                                       (vreset! *cursor-prev pos)
+                                       (vreset! *cursor-prev @*cursor-curr))
+                                     (vreset! *cursor-curr pos)))]
         (gobj/set textarea-ref codemirror-ref-name editor)
         (when (= mode "calc")
           (.on editor "change" (fn [_cm _e]
@@ -456,18 +468,55 @@
                                     (not (gobj/get cm "escPressed")))
                                (code-handler/save-code-editor!))
                              (state/set-block-component-editing-mode! false)
-                             (state/set-state! :editor/code-block-context nil)))
+                             (state/set-state! :editor/code-block-context nil)
+                             (vreset! *cursor-curr nil)
+                             (vreset! *cursor-prev nil)))
         (.on editor "focus" (fn [_e]
+                              (when-not (= (:block/uuid edit-block) (:block/uuid (state/get-edit-block)))
+                                (editor-handler/edit-block! edit-block :max))
+                              (state/set-editing-block-dom-id! (:block-parent-id config))
                               (state/set-block-component-editing-mode! true)
                               (state/set-state! :editor/code-block-context
                                                 {:editor editor
                                                  :config config
                                                  :state state})))
+        (.on editor "cursorActivity" update-cursor-state!)
         (.addEventListener element "keydown" (fn [e]
                                                (let [key-code (.-code e)
                                                      meta-or-ctrl-pressed? (or (.-ctrlKey e) (.-metaKey e))
                                                      shifted? (.-shiftKey e)]
                                                  (cond
+                                                   (contains? #{"ArrowLeft" "ArrowRight"} key-code)
+                                                   (let [direction (if (= "ArrowLeft" key-code) :left :right)
+                                                         line (when-let [line (:line @*cursor-curr)]
+                                                                (.getLine (.-doc editor) line))]
+                                                     (when (and (= @*cursor-prev @*cursor-curr)
+                                                                (or (and direction (nil? @*cursor-curr))
+                                                                    (case direction
+                                                                      :left (and (zero? (:line @*cursor-curr))
+                                                                                 (zero? (:ch  @*cursor-curr)))
+                                                                      :right (and (= (:line @*cursor-curr) (.lastLine editor))
+                                                                                  (= (count line) (:ch @*cursor-curr)))
+                                                                      false)))
+                                                       (editor-handler/move-to-block-when-cross-boundary direction {}))
+                                                     (update-cursor-state!))
+
+                                                   (contains? #{"ArrowUp" "ArrowDown"} key-code)
+                                                   (let [direction (if (= "ArrowUp" key-code) :up :down)
+                                                         line (when-let [line (:line @*cursor-curr)]
+                                                                (.getLine (.-doc editor) line))]
+                                                     (when (and (= @*cursor-prev @*cursor-curr)
+                                                                (or (and direction (nil? @*cursor-curr))
+                                                                    (case direction
+                                                                      :up (and (zero? (:line @*cursor-curr))
+                                                                               (zero? (:ch  @*cursor-curr)))
+                                                                      :down (and (= (:line @*cursor-curr) (.lastLine editor))
+                                                                                 (= (count line) (:ch @*cursor-curr)))
+                                                                      false)))
+                                                       (editor-handler/move-cross-boundary-up-down
+                                                        direction {:input textarea
+                                                                   :pos [direction 0]}))
+                                                     (update-cursor-state!))
                                                    meta-or-ctrl-pressed?
                                                    ;; prevent default behavior of browser
                                                    ;; Cmd + [ => Go back in browser, outdent in CodeMirror
@@ -478,15 +527,15 @@
                                                      nil)
                                                    shifted?
                                                    (case key-code
+                                                     ;; create new block
                                                      "Enter"
                                                      (do
                                                        (util/stop e)
                                                        (when-let [blockid (some-> (.-target e) (.closest "[blockid]") (.getAttribute "blockid"))]
                                                          (code-handler/save-code-editor!)
-                                                         (js/setTimeout
-                                                          #(editor-handler/api-insert-new-block! ""
-                                                                                                 {:block-uuid (uuid blockid)
-                                                                                                  :sibling? true}) 32)))
+                                                         (util/schedule #(editor-handler/api-insert-new-block! ""
+                                                                                                               {:block-uuid (uuid blockid)
+                                                                                                                :sibling? true}))))
                                                      nil)))))
         (.addEventListener element "pointerdown"
                            (fn [e]

+ 14 - 14
src/main/frontend/format/block.cljs

@@ -29,13 +29,13 @@ and handles unexpected failure."
                                              :db-graph-mode? (config/db-based-graph? repo)})]
         (if (config/db-based-graph? repo)
           (map (fn [block]
-                (cond-> (dissoc block :block/properties :block/macros :block/properties-order)
-                  (:block/properties block)
-                  (merge (update-keys (:block/properties block)
-                                      (fn [k]
-                                        (or ({:heading :logseq.property/heading} k)
-                                            (throw (ex-info (str "Don't know how to save graph-parser property " (pr-str k)) {}))))))))
-              blocks)
+                 (cond-> (dissoc block :block/properties :block/macros :block/properties-order)
+                   (:block/properties block)
+                   (merge (update-keys (:block/properties block)
+                                       (fn [k]
+                                         (or ({:heading :logseq.property/heading} k)
+                                             (throw (ex-info (str "Don't know how to save graph-parser property " (pr-str k)) {}))))))))
+               blocks)
           blocks))
       (catch :default e
         (log/error :exception e)
@@ -131,13 +131,13 @@ and handles unexpected failure."
     (if (= typ "Paragraph")
       (let [indexed-paras (map-indexed vector paras)]
         [typ (->> (filter
-                            #(let [[index value] %]
-                               (not (and (> index 0)
-                                         (= value ["Break_Line"])
-                                         (contains? #{"Timestamp" "Macro"}
-                                                    (first (nth paras (dec index)))))))
-                            indexed-paras)
-                           (map #(last %)))])
+                   #(let [[index value] %]
+                      (not (and (> index 0)
+                                (= value ["Break_Line"])
+                                (contains? #{"Timestamp" "Macro"}
+                                           (first (nth paras (dec index)))))))
+                   indexed-paras)
+                  (map #(last %)))])
       ast)))
 
 (defn trim-break-lines!

+ 9 - 6
src/main/frontend/handler/block.cljs

@@ -171,12 +171,14 @@
     (state/set-editor-last-input-time! repo (util/time-ms))))
 
 (defn- edit-block-aux
-  [repo block content text-range {:keys [container-id]}]
+  [repo block content text-range {:keys [container-id direction event pos]}]
   (when block
     (let [container-id (or container-id
                            (state/get-current-editor-container-id)
                            :unknown-container)]
-      (state/set-editing! (str "edit-block-" (:block/uuid block)) content block text-range {:container-id container-id}))
+      (state/set-editing! (str "edit-block-" (:block/uuid block)) content block text-range
+                          {:db (db/get-db)
+                           :container-id container-id :direction direction :event event :pos pos}))
     (mark-last-input-time! repo)))
 
 (defn sanity-block-content
@@ -187,12 +189,13 @@
         (drawer/remove-logbook))))
 
 (defn edit-block!
-  [block pos & {:keys [_container-id custom-content tail-len]
-                :or {tail-len 0}
+  [block pos & {:keys [_container-id custom-content tail-len save-code-editor?]
+                :or {tail-len 0
+                     save-code-editor? true}
                 :as opts}]
   (when (and (not config/publishing?) (:block/uuid block))
     (p/do!
-     (state/pub-event! [:editor/save-code-editor])
+     (when save-code-editor? (state/pub-event! [:editor/save-code-editor]))
      (when (not= (:block/uuid block) (:block/uuid (state/get-edit-block)))
        (state/clear-edit! {:clear-editing-block? false}))
      (when-let [block-id (:block/uuid block)]
@@ -214,7 +217,7 @@
                           (subs content 0 pos))
              content (sanity-block-content repo (:block/format block) content)]
          (state/clear-selection!)
-         (edit-block-aux repo block content text-range opts))))))
+         (edit-block-aux repo block content text-range (assoc opts :pos pos)))))))
 
 (defn- get-original-block-by-dom
   [node]

+ 19 - 12
src/main/frontend/handler/editor.cljs

@@ -940,6 +940,11 @@
     (set-blocks-id! [block-id])
     (util/copy-to-clipboard! (tap-clipboard block-id)))))
 
+(defn copy-block-content!
+  [block]
+  (util/copy-to-clipboard! (:block/title block))
+  (notification/show! "Copied!" :success))
+
 (defn select-block!
   [block-uuid]
   (block-handler/select-block! block-uuid))
@@ -2555,11 +2560,10 @@
       (util/scroll-to-block sibling-block)
       (state/exit-editing-and-set-selected-blocks! [sibling-block]))))
 
-(defn- move-cross-boundary-up-down
+(defn move-cross-boundary-up-down
   [direction move-opts]
-  (when-let [input (state/get-input)]
-    (let [line-pos (util/get-line-pos (.-value input) (util/get-selection-start input))
-          repo (state/get-current-repo)
+  (when-let [input (or (:input move-opts) (state/get-input))]
+    (let [repo (state/get-current-repo)
           f (case direction
               :up util/get-prev-block-non-collapsed
               :down util/get-next-block-non-collapsed)
@@ -2571,15 +2575,17 @@
           (let [container-id (some-> (dom/attr sibling-block "containerid") js/parseInt)
                 value (state/get-edit-content)]
             (p/do!
-             (when (not= (clean-content! repo format title)
-                         (string/trim value))
+             (when (and
+                    (not (state/block-component-editing?))
+                    (not= (clean-content! repo format title)
+                          (string/trim value)))
                (save-block! repo uuid value))
 
              (let [new-uuid (cljs.core/uuid sibling-block-id)
                    block (db/entity [:block/uuid new-uuid])]
                (edit-block! block
                             (or (:pos move-opts)
-                                [direction line-pos])
+                                [direction (util/get-line-pos (.-value input) (util/get-selection-start input))])
                             {:container-id container-id
                              :direction direction})))))
         (case direction
@@ -2608,11 +2614,11 @@
         (cursor/move-cursor-up input)
         (cursor/move-cursor-down input)))))
 
-(defn- move-to-block-when-cross-boundary
-  [direction]
+(defn move-to-block-when-cross-boundary
+  [direction {:keys [block]}]
   (let [up? (= :left direction)
         pos (if up? :max 0)
-        {:block/keys [format uuid] :as block} (state/get-edit-block)
+        {:block/keys [format uuid] :as block} (or block (state/get-edit-block))
         repo (state/get-current-repo)
         editing-block (gdom/getElement (state/get-editing-block-dom-id))
         f (if up? util/get-prev-block-non-collapsed util/get-next-block-non-collapsed)
@@ -2658,7 +2664,7 @@
 
         (or (and left? (cursor/start? input))
             (and right? (cursor/end? input)))
-        (move-to-block-when-cross-boundary direction)
+        (move-to-block-when-cross-boundary direction {})
 
         :else
         (if left?
@@ -3284,7 +3290,8 @@
       (util/stop e)
       (let [block {:block/uuid block-id}
             left? (= direction :left)
-            opts {:container-id (some-> node (dom/attr "containerid") (parse-long))}]
+            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]

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

@@ -55,6 +55,7 @@
             [frontend.handler.code :as code-handler]
             [frontend.handler.db-based.rtc :as rtc-handler]
             [frontend.handler.graph :as graph-handler]
+            [frontend.handler.db-based.property :as db-property-handler]
             [frontend.mobile.core :as mobile]
             [frontend.mobile.graph-picker :as graph-picker]
             [frontend.mobile.util :as mobile-util]
@@ -80,7 +81,9 @@
             [logseq.db :as ldb]
             [frontend.persist-db :as persist-db]
             [frontend.handler.export :as export]
-            [frontend.extensions.fsrs :as fsrs]))
+            [frontend.extensions.fsrs :as fsrs]
+            [frontend.storage :as storage]
+            [frontend.modules.outliner.ui :as ui-outliner-tx]))
 
 ;; TODO: should we move all events here?
 
@@ -885,6 +888,22 @@
 (defmethod handle :editor/save-code-editor [_]
   (code-handler/save-code-editor!))
 
+(defmethod handle :editor/focus-code-editor [[_ editing-block container]]
+  (when-let [^js cm (util/get-cm-instance container)]
+    (when-not (.hasFocus cm)
+      (let [cursor-pos (some-> (:editor/cursor-range @state/state) (deref) (count))
+            direction (:block.editing/direction editing-block)
+            pos (:block.editing/pos editing-block)
+            to-line (case direction
+                      :up (.lastLine cm)
+                      (case pos
+                        :max (.lastLine cm)
+                        0))]
+                 ;; move to friendly cursor
+        (doto cm
+          (.focus)
+          (.setCursor to-line (or cursor-pos 0)))))))
+
 (defmethod handle :editor/toggle-children-number-list [[_ block]]
   (when-let [blocks (and block (db-model/get-block-immediate-children (state/get-current-repo) (:block/uuid block)))]
     (editor-handler/toggle-blocks-as-own-order-list! blocks)))
@@ -945,6 +964,36 @@
                               {:id :property-dialog
                                :align "start"})))))))
 
+(defmethod handle :editor/upsert-type-block [[_ {:keys [block type]}]]
+  (p/do!
+   (editor-handler/save-current-block!)
+   (p/delay 16)
+   (let [block (db/entity (:db/id block))
+         block-type (:logseq.property.node/display-type block)
+         block-title (:block/title block)
+         latest-code-lang (storage/get :latest-code-lang)
+         turn-type! #(if (and (= (keyword type) :code) latest-code-lang)
+                       (db-property-handler/set-block-properties!
+                        (:block/uuid %)
+                        {:logseq.property.node/display-type (keyword type)
+                         :logseq.property.code/lang latest-code-lang})
+                       (db-property-handler/set-block-property!
+                        (:block/uuid %) :logseq.property.node/display-type (keyword type)))]
+     (p/let [block (if (or (not (nil? block-type))
+                           (not (string/blank? block-title)))
+                     (p/let [result (ui-outliner-tx/transact!
+                                     {:outliner-op :insert-blocks}
+                                     ;; insert a new block
+                                     (let [[_p _ block'] (editor-handler/insert-new-block-aux! {} block "")]
+                                       (turn-type! block')))
+                             result' (ldb/read-transit-str result)]
+                       (when-let [id (:block/uuid (first (:blocks result')))]
+                         (db/entity [:block/uuid id])))
+                     (p/do!
+                      (turn-type! block)
+                      (db/entity [:block/uuid (:block/uuid block)])))]
+       (js/setTimeout #(editor-handler/edit-block! block :max) 100)))))
+
 (rum/defc multi-tabs-dialog
   []
   (let [word (if (util/electron?) "window" "tab")]

+ 100 - 96
src/main/frontend/state.cljs

@@ -164,6 +164,7 @@
       :editor/cursor-range                   (atom nil)
       :editor/container-id                   (atom nil)
       :editor/next-edit-block                (atom nil)
+      :editor/raw-mode-block                 (atom nil)
 
       :selection/mode                        (atom false)
       ;; Warning: blocks order is determined when setting this attribute
@@ -384,8 +385,8 @@
                       [(<= ?d ?today)]]
              :inputs [:14d :today]
              :result-transform '(fn [result]
-                                 (sort-by (fn [h]
-                                            (get h :block/priority "Z")) result))
+                                  (sort-by (fn [h]
+                                             (get h :block/priority "Z")) result))
              :group-by-page? false
              :collapsed? false}
             {:title "📅 NEXT"
@@ -443,10 +444,10 @@
   (->> configs
        (filter map?)
        (apply merge-with
-         (fn merge-config [current new]
-           (if (and (map? current) (map? new))
-             (merge current new)
-             new)))))
+              (fn merge-config [current new]
+                (if (and (map? current) (map? new))
+                  (merge current new)
+                  new)))))
 
 (defn get-global-config
   []
@@ -482,13 +483,13 @@ should be done through this fn in order to get global config and config defaults
   (or (not @publishing?) (:publishing/enable-editing? (get-config))))
 
 (defonce built-in-macros
-         {"img" "[:img.$4 {:src \"$1\" :style {:width $2 :height $3}}]"})
+  {"img" "[:img.$4 {:src \"$1\" :style {:width $2 :height $3}}]"})
 
 (defn get-macros
   []
   (merge
-    built-in-macros
-    (:macros (get-config))))
+   built-in-macros
+   (:macros (get-config))))
 
 (defn set-assets-alias-enabled!
   [v]
@@ -534,9 +535,9 @@ should be done through this fn in order to get global config and config defaults
    (get-preferred-format (get-current-repo)))
   ([repo-url]
    (keyword
-     (or
-      (common-config/get-preferred-format (get-config repo-url))
-      (get-in @state [:me :preferred_format] "markdown")))))
+    (or
+     (common-config/get-preferred-format (get-config repo-url))
+     (get-in @state [:me :preferred_format] "markdown")))))
 
 (defn markdown?
   []
@@ -546,16 +547,16 @@ should be done through this fn in order to get global config and config defaults
 (defn get-pages-directory
   []
   (or
-    (when-let [repo (get-current-repo)]
-      (:pages-directory (get-config repo)))
-    "pages"))
+   (when-let [repo (get-current-repo)]
+     (:pages-directory (get-config repo)))
+   "pages"))
 
 (defn get-journals-directory
   []
   (or
-    (when-let [repo (get-current-repo)]
-      (:journals-directory (get-config repo)))
-    "journals"))
+   (when-let [repo (get-current-repo)]
+     (:journals-directory (get-config repo)))
+   "journals"))
 
 (defn get-whiteboards-directory
   []
@@ -576,13 +577,13 @@ should be done through this fn in order to get global config and config defaults
 (defn get-preferred-workflow
   []
   (keyword
-    (or
-      (when-let [workflow (:preferred-workflow (get-config))]
-        (let [workflow (name workflow)]
-          (if (util/safe-re-find #"now|NOW" workflow)
-            :now
-            :todo)))
-      (get-in @state [:me :preferred_workflow] :now))))
+   (or
+    (when-let [workflow (:preferred-workflow (get-config))]
+      (let [workflow (name workflow)]
+        (if (util/safe-re-find #"now|NOW" workflow)
+          :now
+          :todo)))
+    (get-in @state [:me :preferred_workflow] :now))))
 
 (defn get-preferred-todo
   []
@@ -601,8 +602,8 @@ should be done through this fn in order to get global config and config defaults
     (if (sqlite-util/db-based-graph? repo)
       (when-let [conn (db-conn-state/get-conn repo)]
         (get (d/entity @conn :logseq.class/Journal)
-           :logseq.property.journal/title-format
-           "MMM do, yyyy"))
+             :logseq.property.journal/title-format
+             "MMM do, yyyy"))
       (common-config/get-date-formatter (get-config)))))
 
 (defn shortcuts []
@@ -626,18 +627,18 @@ should be done through this fn in order to get global config and config defaults
 (defn get-ref-open-blocks-level
   []
   (or
-    (when-let [value (:ref/default-open-blocks-level (get-config))]
-      (when (integer? value)
-        value))
-    2))
+   (when-let [value (:ref/default-open-blocks-level (get-config))]
+     (when (integer? value)
+       value))
+   2))
 
 (defn get-linked-references-collapsed-threshold
   []
   (or
-    (when-let [value (:ref/linked-references-collapsed-threshold (get-config))]
-      (when (integer? value)
-        value))
-    100))
+   (when-let [value (:ref/linked-references-collapsed-threshold (get-config))]
+     (when (integer? value)
+       value))
+   100))
 
 (defn get-export-bullet-indentation
   []
@@ -1463,12 +1464,12 @@ Similar to re-frame subscriptions"
   []
   (when (util/electron?)
     (js/window.apis.setUpdatesCallback
-      (fn [_ args]
-        (let [data (bean/->clj args)
-              pending? (not= (:type data) "completed")]
-          (set-state! :electron/updater-pending? pending?)
-          (when pending? (set-state! :electron/updater data))
-          nil)))))
+     (fn [_ args]
+       (let [data (bean/->clj args)
+             pending? (not= (:type data) "completed")]
+         (set-state! :electron/updater-pending? pending?)
+         (when pending? (set-state! :electron/updater data))
+         nil)))))
 
 (defn set-file-component!
   [component]
@@ -1538,17 +1539,17 @@ Similar to re-frame subscriptions"
            idx (and id (first (keep-indexed #(when (= (:modal/id %2) id) %1)
                                             modals)))
            input (medley/filter-vals
-                   #(not (nil? %1))
-                   {:modal/id            id
-                    :modal/label         (if label (name label) "")
-                    :modal/class         (if center? "as-center" "")
-                    :modal/payload       payload
-                    :modal/show?         (if (boolean? show?) show? true)
-                    :modal/panel-content panel-content
-                    :modal/close-btn?    close-btn?
-                    :modal/close-backdrop? (if (boolean? close-backdrop?) close-backdrop? true)})]
+                  #(not (nil? %1))
+                  {:modal/id            id
+                   :modal/label         (if label (name label) "")
+                   :modal/class         (if center? "as-center" "")
+                   :modal/payload       payload
+                   :modal/show?         (if (boolean? show?) show? true)
+                   :modal/panel-content panel-content
+                   :modal/close-btn?    close-btn?
+                   :modal/close-backdrop? (if (boolean? close-backdrop?) close-backdrop? true)})]
        (swap! state update-in
-         [:modal/subsets (or idx (count modals))]
+              [:modal/subsets (or idx (count modals))]
               merge input)
        (:modal/subsets @state)))))
 
@@ -1563,7 +1564,7 @@ Similar to re-frame subscriptions"
        (if (and id (not (string/blank? mid)) (= id mid))
          (close-modal!)
          (when-let [idx (if id (first (keep-indexed #(when (= (:modal/id %2) id) %1) modals))
-                          (dec (count modals)))]
+                            (dec (count modals)))]
            (swap! state assoc :modal/subsets (into [] (medley/remove-nth idx modals)))))))
    (:modal/subsets @state)))
 
@@ -1639,7 +1640,7 @@ Similar to re-frame subscriptions"
 (defn toggle-left-sidebar!
   []
   (set-left-sidebar-open!
-    (not (get-left-sidebar-open?))))
+   (not (get-left-sidebar-open?))))
 
 (defn set-developer-mode!
   [value]
@@ -1726,7 +1727,7 @@ Similar to re-frame subscriptions"
   (when-let [resource (get-plugin-resource pid type key)]
     (let [resource (assoc resource (keyword attr) val)]
       (set-state!
-        [:plugin/installed-resources (keyword pid) (keyword type) key] resource)
+       [:plugin/installed-resources (keyword pid) (keyword type) key] resource)
       resource)))
 
 (defn get-plugin-services
@@ -1791,8 +1792,8 @@ Similar to re-frame subscriptions"
      (set-state!
       [:plugin/installed-hooks hook]
       (assoc
-        ((fnil identity {}) (get-in @state [:plugin/installed-hooks hook]))
-        pid opts)) true)))
+       ((fnil identity {}) (get-in @state [:plugin/installed-hooks hook]))
+       pid opts)) true)))
 
 (defn uninstall-plugin-hook
   [pid hook-or-all]
@@ -2033,7 +2034,7 @@ Similar to re-frame subscriptions"
    (set-selection-blocks! blocks direction)))
 
 (defn set-editing!
-  [edit-input-id content block cursor-range & {:keys [move-cursor? container-id property-block]
+  [edit-input-id content block cursor-range & {:keys [db move-cursor? container-id property-block direction event pos]
                                                :or {move-cursor? true}}]
   (when-not (exists? js/process)
     (if (> (count content)
@@ -2043,39 +2044,43 @@ Similar to re-frame subscriptions"
           (util/scroll-to-element (gobj/get (first elements) "id")))
         (exit-editing-and-set-selected-blocks! elements))
       (when (and edit-input-id block
-                   (or
-                    (publishing-enable-editing?)
-                    (not @publishing?)))
-          (let [block-element (gdom/getElement (string/replace edit-input-id "edit-block" "ls-block"))
-                container (util/get-block-container block-element)
-                block (if container
-                        (assoc block
-                               :block.temp/container (gobj/get container "id"))
-                        block)
-                content (string/trim (or content ""))]
-            (assert (and container-id (:block/uuid block))
-                    "container-id or block uuid is missing")
-            (set-state! :editor/block-refs #{})
-            (if property-block
-              (set-editing-block-id! [container-id (:block/uuid property-block) (:block/uuid block)])
-              (set-editing-block-id! [container-id (:block/uuid block)]))
-            (set-state! :editor/container-id container-id)
-            (set-state! :editor/block block)
-            (set-state! :editor/content content :path-in-sub-atom (:block/uuid block))
-            (set-state! :editor/last-key-code nil)
-            (set-state! :editor/set-timestamp-block nil)
-            (set-state! :editor/cursor-range cursor-range)
-
-            (when-let [input (gdom/getElement edit-input-id)]
-              (let [pos (count cursor-range)]
-                (when content
-                  (util/set-change-value input content))
-
-                (when move-cursor?
-                  (cursor/move-cursor-to input pos))
-
-                (when (or (util/mobile?) (mobile-util/native-platform?))
-                  (set-state! :mobile/show-action-bar? false)))))))))
+                 (or
+                  (publishing-enable-editing?)
+                  (not @publishing?)))
+        (let [block-element (gdom/getElement (string/replace edit-input-id "edit-block" "ls-block"))
+              container (util/get-block-container block-element)
+              block (if container
+                      (assoc block
+                             :block.temp/container (gobj/get container "id"))
+                      block)
+              block (assoc block :block.editing/direction direction
+                           :block.editing/event event
+                           :block.editing/pos pos)
+              content (string/trim (or content ""))]
+          (assert (and container-id (:block/uuid block))
+                  "container-id or block uuid is missing")
+          (set-state! :editor/block-refs #{})
+          (if property-block
+            (set-editing-block-id! [container-id (:block/uuid property-block) (:block/uuid block)])
+            (set-editing-block-id! [container-id (:block/uuid block)]))
+          (set-state! :editor/container-id container-id)
+          (set-state! :editor/block block)
+          (set-state! :editor/content content :path-in-sub-atom (:block/uuid block))
+          (set-state! :editor/last-key-code nil)
+          (set-state! :editor/set-timestamp-block nil)
+          (set-state! :editor/cursor-range cursor-range)
+          (when (= :code (:logseq.property.node/display-type (d/entity db (:db/id block))))
+            (pub-event! [:editor/focus-code-editor block block-element]))
+          (when-let [input (gdom/getElement edit-input-id)]
+            (let [pos (count cursor-range)]
+              (when content
+                (util/set-change-value input content))
+
+              (when (and move-cursor? (not (block-component-editing?)))
+                (cursor/move-cursor-to input pos))
+
+              (when (or (util/mobile?) (mobile-util/native-platform?))
+                (set-state! :mobile/show-action-bar? false)))))))))
 
 (defn action-bar-open?
   []
@@ -2126,10 +2131,10 @@ Similar to re-frame subscriptions"
   ([theme?] (get-enabled?-installed-plugins theme? true false false))
   ([theme? enabled? include-unpacked? include-all?]
    (filterv
-     #(and (if include-unpacked? true (:iir %))
-           (if-not (boolean? enabled?) true (= (not enabled?) (boolean (get-in % [:settings :disabled]))))
-           (or include-all? (if (boolean? theme?) (= (boolean theme?) (:theme %)) true)))
-     (vals (:plugin/installed-plugins @state)))))
+    #(and (if include-unpacked? true (:iir %))
+          (if-not (boolean? enabled?) true (= (not enabled?) (boolean (get-in % [:settings :disabled]))))
+          (or include-all? (if (boolean? theme?) (= (boolean theme?) (:theme %)) true)))
+    (vals (:plugin/installed-plugins @state)))))
 
 (defn lsp-enabled?-or-theme
   []
@@ -2194,7 +2199,6 @@ Similar to re-frame subscriptions"
     (->> (sub :sidebar/blocks)
          (filter #(= (first %) current-repo)))))
 
-
 (defn toggle-collapsed-block!
   [block-id]
   (let [current-repo (get-current-repo)]
@@ -2277,7 +2281,7 @@ Similar to re-frame subscriptions"
   [m]
   (update-state! [:graph/parsing-state (get-current-repo)]
                  (if (fn? m) m
-                   (fn [old-value] (merge old-value m)))))
+                     (fn [old-value] (merge old-value m)))))
 
 (defn http-proxy-enabled-or-val? []
   (when-let [{:keys [type protocol host port] :as agent-opts} (sub [:electron/user-cfgs :settings/agent])]

+ 6 - 0
src/main/frontend/util.cljc

@@ -1536,3 +1536,9 @@ Arg *stop: atom, reset to true to stop the loop"
                 (js->clj)
                 (into {})
                 (walk/keywordize-keys)))))))
+
+#?(:cljs
+   (defn get-cm-instance
+     [^js target]
+     (when target
+       (some-> target (.querySelector ".CodeMirror") (.-CodeMirror)))))

+ 2 - 1
src/main/frontend/worker/db/migrate.cljs

@@ -300,7 +300,8 @@
         :fix add-query-property-to-query-tag}]
    [26 {:properties [:logseq.property.node/type]}]
    [27 {:properties [:logseq.property.code/mode]}]
-   [28 {:fix (rename-properties {:logseq.property.node/type :logseq.property.node/display-type})}]])
+   [28 {:fix (rename-properties {:logseq.property.node/type :logseq.property.node/display-type})}]
+   [29 {:properties [:logseq.property.code/lang]}]])
 
 (let [max-schema-version (apply max (map first schema-version->updates))]
   (assert (<= db-schema/version max-schema-version))

+ 15 - 15
yarn.lock

@@ -2134,10 +2134,10 @@ code-point-at@^1.0.0:
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
 
[email protected]3:
-  version "5.65.13"
-  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.13.tgz#c098a6f409db8b5a7c5722788bd9fa3bb2367f2e"
-  integrity sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==
[email protected]8:
+  version "5.65.18"
+  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.18.tgz#d7146e4271135a9b4adcd023a270185457c9c428"
+  integrity sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA==
 
 collection-map@^1.0.0:
   version "1.0.0"
@@ -3130,7 +3130,7 @@ escape-html@~1.0.3:
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
-escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
@@ -3974,6 +3974,16 @@ gulp-cli@^2.2.0:
     v8flags "^3.2.0"
     yargs "^7.1.0"
 
+gulp-postcss@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-10.0.0.tgz#a88d7c6602f8a8c94aaa9f28ac3a68def00c7ada"
+  integrity sha512-z1RF2RJEX/BvFsKN11PXai8lRmihZTiHnlJf7Zu8uHaA/Q7Om4IeN8z1NtMAW5OiLwUY02H0DIFl9tHl0CNSgA==
+  dependencies:
+    fancy-log "^2.0.0"
+    plugin-error "^2.0.1"
+    postcss-load-config "^5.0.0"
+    vinyl-sourcemaps-apply "^0.2.1"
+
 gulp-replace@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/gulp-replace/-/gulp-replace-1.1.4.tgz#06a0e9ee36f30e343c1e0a2dd760ec32c8a3d3b2"
@@ -3985,16 +3995,6 @@ gulp-replace@^1.1.4:
     replacestream "^4.0.3"
     yargs-parser ">=5.0.0-security.0"
 
-gulp-postcss@^10.0.0:
-  version "10.0.0"
-  resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-10.0.0.tgz#a88d7c6602f8a8c94aaa9f28ac3a68def00c7ada"
-  integrity sha512-z1RF2RJEX/BvFsKN11PXai8lRmihZTiHnlJf7Zu8uHaA/Q7Om4IeN8z1NtMAW5OiLwUY02H0DIFl9tHl0CNSgA==
-  dependencies:
-    fancy-log "^2.0.0"
-    plugin-error "^2.0.1"
-    postcss-load-config "^5.0.0"
-    vinyl-sourcemaps-apply "^0.2.1"
-
 gulp@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa"