From 7cb91444221229f99be2c8f8e91337dd06f8a64e Mon Sep 17 00:00:00 2001 From: megayu Date: Tue, 12 May 2026 16:09:06 +0800 Subject: [PATCH] enhance(editor): support dollar autopair for markdown math (#12618) * feat(editor): add support for dollar sign in autopair feature * fix(editor): handle dollar autopair for markdown math * feat(help): add inline math example * fix(editor): double dollar autopair behavior --- .../frontend/components/shortcut_help.cljs | 4 +- src/main/frontend/handler/editor.cljs | 15 +++-- src/resources/dicts/en.edn | 1 + src/resources/dicts/zh-cn.edn | 1 + src/test/frontend/handler/editor_test.cljs | 67 +++++++++++++++++++ 5 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/main/frontend/components/shortcut_help.cljs b/src/main/frontend/components/shortcut_help.cljs index 3b2bd02a3b..6a479e945d 100644 --- a/src/main/frontend/components/shortcut_help.cljs +++ b/src/main/frontend/components/shortcut_help.cljs @@ -37,7 +37,7 @@ [:td.text-right [:code (t :help/context-menu-action)]]]]]) (defn markdown-syntax [] - (let [list [:bold :italics :del :mark :latex :code :link :pre :img] + (let [list [:bold :italics :del :mark :math :latex :code :link :pre :img] title (t :help/markdown-syntax) learn-more "https://www.markdownguide.org/basic-syntax" raw {:bold (str "**" (t :format/bold) "**") @@ -45,6 +45,7 @@ :link "[Link](https://www.example.com)" :del (str "~~" (t :format/strikethrough) "~~") :mark (str "^^" (t :format/highlight) "^^") + :math (str (t :help/inline-math-example-prefix) " $E = mc^2$") :latex "$$E = mc^2$$" :code (str "`" (t :format/code) "`") :pre "```clojure\n (println \"Hello world!\")\n```" @@ -55,6 +56,7 @@ :link [:a {:href "https://www.example.com"} (t :ui/link)] :del [:del (t :format/strikethrough)] :mark [:mark (t :format/highlight)] + :math [:span (t :help/inline-math-example-prefix) " " (latex/latex "E = mc^2" false false)] :latex (latex/latex "E = mc^2" true false) :code [:code (t :format/code)] :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")") diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 9376cc0647..b7e93fbc2b 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1486,6 +1486,7 @@ "~" "~" "*" "*" "_" "_" + "$" "$" "^" "^" "=" "=" "/" "/" @@ -1501,14 +1502,13 @@ (def delete-map (assoc autopair-map - "$" "$" ":" ":")) (defn- autopair [input-id prefix _format _option] - (let [value (get autopair-map prefix) + (let [suffix (get autopair-map prefix) selected (util/get-selected-text) - postfix (str selected value) + postfix (str selected suffix) value (str prefix postfix) input (gdom/getElement input-id)] (when value @@ -2649,6 +2649,11 @@ (= "#" (util/nth-safe value (dec pos)))) (state/clear-editor-action!) + (and (= "$" key) (string/blank? (util/get-selected-text))) + (do + (util/stop e) + (commands/simple-insert! input-id "$$" {:backward-pos 1})) + (and (contains? (set/difference (set (keys reversed-autopair-map)) #{"`"}) key) @@ -2693,10 +2698,6 @@ (double-chars-typed? value pos key sym)) (state/pub-event! [:editor/new-property]) - (let [sym "$"] - (double-chars-typed? value pos key sym)) - (commands/simple-insert! input-id "$$" {:backward-pos 2}) - (let [sym "^"] (double-chars-typed? value pos key sym)) (commands/simple-insert! input-id "^^" {:backward-pos 2}) diff --git a/src/resources/dicts/en.edn b/src/resources/dicts/en.edn index 4d44747346..bb726a42cb 100644 --- a/src/resources/dicts/en.edn +++ b/src/resources/dicts/en.edn @@ -799,6 +799,7 @@ :help/feature "Feature request" :help/forum-community "Forum community" :help/handbook "Handbook" + :help/inline-math-example-prefix "inline" :help/learn-more "Learn more" :help/markdown-syntax "Markdown syntax" :help/open-link-in-sidebar "Open link in sidebar" diff --git a/src/resources/dicts/zh-cn.edn b/src/resources/dicts/zh-cn.edn index 0819bf3b8b..9d2ed2eeb3 100644 --- a/src/resources/dicts/zh-cn.edn +++ b/src/resources/dicts/zh-cn.edn @@ -795,6 +795,7 @@ :help/feature "功能建议" :help/forum-community "论坛讨论" :help/handbook "手册" + :help/inline-math-example-prefix "行内" :help/learn-more "了解更多" :help/markdown-syntax "Markdown 语法" :help/open-link-in-sidebar "在侧边栏打开" diff --git a/src/test/frontend/handler/editor_test.cljs b/src/test/frontend/handler/editor_test.cljs index 46ba97d7f0..ec5efb8459 100644 --- a/src/test/frontend/handler/editor_test.cljs +++ b/src/test/frontend/handler/editor_test.cljs @@ -8,6 +8,7 @@ [frontend.test.helper :as test-helper] [frontend.util :as util] [frontend.util.cursor :as cursor] + [goog.dom :as gdom] [logseq.outliner.core :as outliner-core])) (use-fixtures :each test-helper/start-and-destroy-db) @@ -188,6 +189,72 @@ :cursor-pos 2 :key "a"})))) +(deftest keydown-not-matched-handler-wraps-selected-text-with-single-dollar + (let [content (atom nil) + cursor-pos (atom nil) + selection-range (atom nil) + input #js {:id "edit-block-test" + :value "inline math" + :setSelectionRange (fn [start end] + (reset! selection-range [start end]))} + event #js {:key "$" + :ctrlKey false + :metaKey false} + selected "math"] + (with-redefs [state/get-edit-input-id (constantly "edit-block-test") + state/get-input (constantly input) + state/get-editor-action (constantly nil) + state/set-state! (constantly nil) + state/set-block-content-and-last-pos! (fn [_input-id value' pos'] + (reset! content value') + (reset! cursor-pos pos')) + gdom/getElement (constantly input) + util/get-selected-text (constantly selected) + util/stop (constantly nil) + cursor/pos (constantly 7) + cursor/move-cursor-to (fn [_ pos' & _] + (reset! cursor-pos pos'))] + ((editor/keydown-not-matched-handler :markdown) event nil) + (is (= "inline $math$" @content)) + (is (= 8 @cursor-pos)) + (is (= [8 12] @selection-range))))) + +(defn- keydown-dollar-without-selection-result + [{:keys [value cursor-pos]}] + (let [content (atom nil) + cursor-pos' (atom nil) + input #js {:id "edit-block-test" + :value value} + event #js {:key "$" + :ctrlKey false + :metaKey false}] + (with-redefs [state/get-edit-input-id (constantly "edit-block-test") + state/get-input (constantly input) + state/get-editor-action (constantly nil) + state/set-state! (constantly nil) + state/set-block-content-and-last-pos! (fn [_input-id value' pos'] + (reset! content value') + (reset! cursor-pos' pos')) + gdom/getElement (constantly input) + util/get-selected-text (constantly "") + util/stop (constantly nil) + cursor/pos (constantly cursor-pos) + cursor/move-cursor-to (fn [_ pos' & _] + (reset! cursor-pos' pos'))] + ((editor/keydown-not-matched-handler :markdown) event nil) + {:content @content + :cursor-pos @cursor-pos'}))) + +(deftest keydown-not-matched-handler-expands-dollar-delimiters-without-selection + (is (= {:content "inline $$" + :cursor-pos 8} + (keydown-dollar-without-selection-result {:value "inline " + :cursor-pos 7}))) + (is (= {:content "inline $$$$" + :cursor-pos 9} + (keydown-dollar-without-selection-result {:value "inline $$" + :cursor-pos 8})))) + (defn- handle-last-input-handler "Spied version of editor/handle-last-input" [{:keys [value cursor-pos]}]