فهرست منبع

refactor: unify :editor/show-xxx and (block) commands to :editor/action

Tienson Qin 3 سال پیش
والد
کامیت
6d0be829a1

+ 2 - 5
src/main/frontend/commands.cljs

@@ -23,9 +23,7 @@
 
 ;; TODO: move to frontend.handler.editor.commands
 
-(defonce *show-commands (atom false))
 (defonce *slash-caret-pos (atom nil))
-(defonce *show-block-commands (atom false))
 (defonce angle-bracket "<")
 (defonce *angle-bracket-caret-pos (atom nil))
 (defonce *current-command (atom nil))
@@ -311,9 +309,8 @@
   [restore-slash-caret-pos?]
   (when restore-slash-caret-pos?
     (reset! *slash-caret-pos nil))
-  (reset! *show-commands false)
+  (state/clear-editor-action!)
   (reset! *angle-bracket-caret-pos nil)
-  (reset! *show-block-commands false)
   (reset! *matched-commands @*initial-commands)
   (reset! *matched-block-commands (block-commands-map)))
 
@@ -476,7 +473,7 @@
                   (str "\n" value)
                   value)]
       (insert! input-id value option)
-      (reset! *show-commands false))))
+      (state/clear-editor-action!))))
 
 (defmethod handle-step :editor/cursor-back [[_ n]]
   (when-let [input-id (state/get-edit-input-id)]

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

@@ -147,7 +147,7 @@
                                    (contains? #{"deadline" "scheduled"}
                                               (string/lower-case current-command)))
         date (state/sub :date-picker/date)]
-    (when (state/sub :editor/show-date-picker?)
+    (when (= :datepicker (state/sub :editor/action))
       [:div#date-time-picker.flex.flex-row {:on-click (fn [e] (util/stop e))
                                             :on-mouse-down (fn [e] (.stopPropagation e))}
        (ui/datepicker

+ 139 - 140
src/main/frontend/components/editor.cljs

@@ -2,7 +2,7 @@
   (:require [clojure.string :as string]
             [goog.string :as gstring]
             [frontend.commands :as commands
-             :refer [*angle-bracket-caret-pos *first-command-group *matched-block-commands *matched-commands *show-block-commands *show-commands *slash-caret-pos]]
+             :refer [*angle-bracket-caret-pos *first-command-group *matched-block-commands *matched-commands *slash-caret-pos]]
             [frontend.components.block :as block]
             [frontend.components.datetime :as datetime-comp]
             [frontend.components.search :as search]
@@ -29,8 +29,8 @@
 
 (rum/defc commands < rum/reactive
   [id format]
-  (let [matched (util/react *matched-commands)]
-    (when (util/react *show-commands)
+  (when (= :commands (state/sub :editor/action))
+    (let [matched (util/react *matched-commands)]
       (ui/auto-complete
        matched
        {:get-group-name
@@ -82,7 +82,7 @@
 
 (rum/defc block-commands < rum/reactive
   [id format]
-  (when (util/react *show-block-commands)
+  (when (= :block-commands (state/get-editor-action))
     (let [matched (util/react *matched-block-commands)]
       (ui/auto-complete
        (map first matched)
@@ -99,59 +99,60 @@
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   "Embedded page searching popup"
   [id format]
-  (when (state/sub :editor/show-page-search?)
-    (let [pos (state/get-editor-last-pos)
-          input (gdom/getElement id)]
-      (when input
-        (let [current-pos (cursor/pos input)
-              edit-content (or (state/sub [:editor/content id]) "")
-              sidebar? (in-sidebar? input)
-              q (or
-                 @editor-handler/*selected-text
-                 (when (state/sub :editor/show-page-search-hashtag?)
-                   (gp-util/safe-subs edit-content pos current-pos))
-                 (when (> (count edit-content) current-pos)
-                   (gp-util/safe-subs edit-content pos current-pos))
-                 "")
-              matched-pages (when-not (string/blank? q)
-                              (editor-handler/get-matched-pages q))
-              matched-pages (cond
-                              (contains? (set (map util/page-name-sanity-lc matched-pages)) (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
-                              matched-pages
-
-                              (string/blank? q)
-                              nil
-
-                              (empty? matched-pages)
-                              (cons (str "New page: " q) matched-pages)
-
-                              ;; reorder, shortest and starts-with first.
-                              :else
-                              (let [matched-pages (remove nil? matched-pages)
-                                    matched-pages (sort-by
-                                                   (fn [m]
-                                                     [(not (gstring/caseInsensitiveStartsWith m q)) (count m)])
-                                                   matched-pages)]
-                                (if (gstring/caseInsensitiveStartsWith (first matched-pages) q)
-                                  (cons (first matched-pages)
-                                        (cons  (str "New page: " q) (rest matched-pages)))
-                                  (cons (str "New page: " q) matched-pages))))]
-          (ui/auto-complete
-           matched-pages
-           {:on-chosen   (page-handler/on-chosen-handler input id q pos format)
-            :on-enter    #(page-handler/page-not-exists-handler input id q current-pos)
-            :item-render (fn [page-name chosen?]
-                           [:div.preview-trigger-wrapper
-                            (block/page-preview-trigger
-                             {:children        [:div (search/highlight-exact-query page-name q)]
-                              :open?           chosen?
-                              :manual?         true
-                              :fixed-position? true
-                              :tippy-distance  24
-                              :tippy-position  (if sidebar? "left" "right")}
-                             page-name)])
-            :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 "Search for a page"]
-            :class       "black"}))))))
+  (let [action (state/sub :editor/action)]
+    (when (contains? #{:page-search :page-search-hashtag} action)
+     (let [pos (state/get-editor-last-pos)
+           input (gdom/getElement id)]
+       (when input
+         (let [current-pos (cursor/pos input)
+               edit-content (or (state/sub [:editor/content id]) "")
+               sidebar? (in-sidebar? input)
+               q (or
+                  @editor-handler/*selected-text
+                  (when (= action :page-search-hashtag)
+                      (gp-util/safe-subs edit-content pos current-pos))
+                  (when (> (count edit-content) current-pos)
+                    (gp-util/safe-subs edit-content pos current-pos))
+                  "")
+               matched-pages (when-not (string/blank? q)
+                               (editor-handler/get-matched-pages q))
+               matched-pages (cond
+                               (contains? (set (map util/page-name-sanity-lc matched-pages)) (util/page-name-sanity-lc (string/trim q)))  ;; if there's a page name fully matched
+                               matched-pages
+
+                               (string/blank? q)
+                               nil
+
+                               (empty? matched-pages)
+                               (cons (str "New page: " q) matched-pages)
+
+                               ;; reorder, shortest and starts-with first.
+                               :else
+                               (let [matched-pages (remove nil? matched-pages)
+                                     matched-pages (sort-by
+                                                    (fn [m]
+                                                      [(not (gstring/caseInsensitiveStartsWith m q)) (count m)])
+                                                    matched-pages)]
+                                 (if (gstring/caseInsensitiveStartsWith (first matched-pages) q)
+                                   (cons (first matched-pages)
+                                         (cons  (str "New page: " q) (rest matched-pages)))
+                                   (cons (str "New page: " q) matched-pages))))]
+           (ui/auto-complete
+            matched-pages
+            {:on-chosen   (page-handler/on-chosen-handler input id q pos format)
+             :on-enter    #(page-handler/page-not-exists-handler input id q current-pos)
+             :item-render (fn [page-name chosen?]
+                            [:div.preview-trigger-wrapper
+                             (block/page-preview-trigger
+                              {:children        [:div (search/highlight-exact-query page-name q)]
+                               :open?           chosen?
+                               :manual?         true
+                               :fixed-position? true
+                               :tippy-distance  24
+                               :tippy-position  (if sidebar? "left" "right")}
+                              page-name)])
+             :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 "Search for a page"]
+             :class       "black"})))))))
 
 (rum/defcs block-search-auto-complete < rum/reactive
   {:init (fn [state]
@@ -189,7 +190,7 @@
                    (state/clear-search-result!)
                    state)}
   [state id _format]
-  (when (state/sub :editor/show-block-search?)
+  (when (= :block-search (state/sub :editor/action))
     (let [pos (state/get-editor-last-pos)
           input (gdom/getElement id)
           [id format] (:rum/args state)
@@ -206,7 +207,7 @@
 (rum/defc template-search < rum/reactive
   {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
   [id _format]
-  (when (state/sub :editor/show-template-search?)
+  (when (= :template-search (state/sub :editor/action))
     (let [pos (state/get-editor-last-pos)
           input (gdom/getElement id)]
       (when input
@@ -237,7 +238,7 @@
       {;; enter
        13 (fn [state e]
             (let [input-value (get state ::input-value)
-                  input-option (get @state/state :editor/show-input)]
+                  input-option (state/get-editor-show-input)]
               (when (seq @input-value)
                 ;; no new line input
                 (util/stop e)
@@ -247,34 +248,35 @@
                   (on-submit command @input-value pos))
                 (reset! input-value nil))))})))
   [state _id on-submit]
-  (when-let [input-option (state/sub :editor/show-input)]
-    (let [{:keys [pos]} (util/react *slash-caret-pos)
-          input-value (get state ::input-value)]
-      (when (seq input-option)
-        (let [command (:command (first input-option))]
-          [:div.p-2.rounded-md.shadow-lg
-           (for [{:keys [id placeholder type autoFocus] :as input-item} input-option]
-             [:div.my-3 {:key id}
-              [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
-               (merge
-                (cond->
-                 {:key           (str "modal-input-" (name id))
-                  :id            (str "modal-input-" (name id))
-                  :type          (or type "text")
-                  :on-change     (fn [e]
-                                   (swap! input-value assoc id (util/evalue e)))
-                  :auto-complete (if (util/chrome?) "chrome-off" "off")}
-                  placeholder
-                  (assoc :placeholder placeholder)
-                  autoFocus
-                  (assoc :auto-focus true))
-                (dissoc input-item :id))]])
-           (ui/button
-            "Submit"
-            :on-click
-            (fn [e]
-              (util/stop e)
-              (on-submit command @input-value pos)))])))))
+  (when (= :input (state/sub :editor/action))
+    (when-let [input-option (state/sub :editor/action-data)]
+     (let [{:keys [pos]} (util/react *slash-caret-pos)
+           input-value (get state ::input-value)]
+       (when (seq input-option)
+         (let [command (:command (first input-option))]
+           [:div.p-2.rounded-md.shadow-lg
+            (for [{:keys [id placeholder type autoFocus] :as input-item} input-option]
+              [:div.my-3 {:key id}
+               [:input.form-input.block.w-full.pl-2.sm:text-sm.sm:leading-5
+                (merge
+                 (cond->
+                   {:key           (str "modal-input-" (name id))
+                    :id            (str "modal-input-" (name id))
+                    :type          (or type "text")
+                    :on-change     (fn [e]
+                                     (swap! input-value assoc id (util/evalue e)))
+                    :auto-complete (if (util/chrome?) "chrome-off" "off")}
+                   placeholder
+                   (assoc :placeholder placeholder)
+                   autoFocus
+                   (assoc :auto-focus true))
+                 (dissoc input-item :id))]])
+            (ui/button
+              "Submit"
+              :on-click
+              (fn [e]
+                (util/stop e)
+                (on-submit command @input-value pos)))]))))))
 
 (rum/defc absolute-modal < rum/static
   [cp set-default-width? {:keys [top left rect]}]
@@ -305,26 +307,27 @@
         to-max-height (if y-overflow-vh? max-height to-max-height)
         pos-rect (when (and (seq rect) editing-key)
                    (:rect (cursor/get-caret-pos (state/get-input))))
-        y-diff (when pos-rect (- (:height pos-rect) (:height rect)))]
+        y-diff (when pos-rect (- (:height pos-rect) (:height rect)))
+        style (merge
+               {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
+                :max-height to-max-height
+                :max-width 700
+                ;; TODO: auto responsive fixed size
+                :width "fit-content"
+                :z-index    11}
+               (when set-default-width?
+                 {:width max-width})
+               (let [^js/HTMLElement editor
+                     (js/document.querySelector ".editor-wrapper")]
+                 (if (<= (.-clientWidth editor) (+ left (if set-default-width? max-width 500)))
+                   {:right 0}
+                   {:left (if (or (nil? y-diff) (and y-diff (= y-diff 0))) left 0)})))]
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
      {:ref *el
       :class (if y-overflow-vh? "is-overflow-vh-y" "")
       :on-mouse-down (fn [e]
                        (.stopPropagation e))
-      :style (merge
-              {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
-               :max-height to-max-height
-               :max-width 700
-               ;; TODO: auto responsive fixed size
-               :width "fit-content"
-               :z-index    11}
-              (when set-default-width?
-                {:width max-width})
-              (let [^js/HTMLElement editor
-                    (js/document.querySelector ".editor-wrapper")]
-                (if (<= (.-clientWidth editor) (+ left (if set-default-width? max-width 500)))
-                  {:right 0}
-                  {:left (if (and y-diff (= y-diff 0)) left 0)})))}
+      :style style}
      cp]))
 
 (rum/defc transition-cp < rum/reactive
@@ -471,43 +474,39 @@
 (rum/defc modals < rum/reactive
   "React to atom changes, find and render the correct modal"
   [id format]
-  (ui/transition-group
-   (cond
-     (and (util/react *show-commands)
-          (not (state/sub :editor/show-page-search?))
-          (not (state/sub :editor/show-block-search?))
-          (not (state/sub :editor/show-template-search?))
-          (not (state/sub :editor/show-input))
-          (not (state/sub :editor/show-zotero))
-          (not (state/sub :editor/show-date-picker?)))
-     (animated-modal "commands" (commands id format) true (util/react *slash-caret-pos))
-
-     (and (util/react *show-block-commands) @*angle-bracket-caret-pos)
-     (animated-modal "block-commands" (block-commands id format) true (util/react *angle-bracket-caret-pos))
-
-     (state/sub :editor/show-page-search?)
-     (animated-modal "page-search" (page-search id format) true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-block-search?)
-     (animated-modal "block-search" (block-search id format) false (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-template-search?)
-     (animated-modal "template-search" (template-search id format) true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-date-picker?)
-     (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-input)
-     (animated-modal "input" (input id
-                                    (fn [command m _pos]
-                                      (editor-handler/handle-command-input command id format m)))
-                     true (util/react *slash-caret-pos))
-
-     (state/sub :editor/show-zotero)
-     (animated-modal "zotero-search" (zotero/zotero-search id) false (util/react *slash-caret-pos))
-
-     :else
-     nil)))
+  (let [action (state/sub :editor/action)
+        slash-pos @*slash-caret-pos]
+    (ui/transition-group
+     (cond
+       (and (= action :commands) slash-pos)
+       (animated-modal "commands" (commands id format) true slash-pos)
+
+       (and (= action :block-commands) @*angle-bracket-caret-pos)
+       (animated-modal "block-commands" (block-commands id format) true (util/react *angle-bracket-caret-pos))
+
+       (contains? #{:page-search :page-search-hashtag} action)
+       (animated-modal "page-search" (page-search id format) true slash-pos)
+
+       (= :block-search action)
+       (animated-modal "block-search" (block-search id format) false slash-pos)
+
+       (= :template-search action)
+       (animated-modal "template-search" (template-search id format) true slash-pos)
+
+       (= :datepicker action)
+       (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false slash-pos)
+
+       (= :input action)
+       (animated-modal "input" (input id
+                                      (fn [command m _pos]
+                                        (editor-handler/handle-command-input command id format m)))
+                       true slash-pos)
+
+       (= :zotero action)
+       (animated-modal "zotero-search" (zotero/zotero-search id) false slash-pos)
+
+       :else
+       nil))))
 
 (rum/defcs box < rum/reactive
   {:init (fn [state]

+ 101 - 118
src/main/frontend/handler/editor.cljs

@@ -6,7 +6,6 @@
             [dommy.core :as dom]
             [frontend.commands :as commands
              :refer [*angle-bracket-caret-pos
-                     *show-block-commands *show-commands
                      *slash-caret-pos]]
             [frontend.config :as config]
             [frontend.date :as date]
@@ -525,7 +524,7 @@
 
 (defn clear-when-saved!
   []
-  (state/clear-editor-show-state!)
+  (state/clear-editor-action!)
   (commands/restore-state true))
 
 (defn get-state
@@ -1292,14 +1291,7 @@
    ;; non English input method
    (when-not (state/editor-in-composition?)
      (when (state/get-current-repo)
-       (when (and (not @commands/*show-commands)
-                  (not @commands/*show-block-commands)
-                  (not (state/get-editor-show-page-search?))
-                  (not (state/get-editor-show-page-search-hashtag?))
-                  (not (state/get-editor-show-block-search?))
-                  (not (state/get-editor-show-date-picker?))
-                  (not (state/get-editor-show-template-search?))
-                  (not (state/get-editor-show-input)))
+       (when-not (state/get-editor-action)
          (try
            (let [input-id (state/get-edit-input-id)
                  block (state/get-edit-block)
@@ -1664,14 +1656,8 @@
 
 (defn auto-complete?
   []
-  (or @*show-commands
-      @*show-block-commands
-      @*asset-uploading?
-      (state/get-editor-show-input)
-      (state/get-editor-show-page-search?)
-      (state/get-editor-show-block-search?)
-      (state/get-editor-show-template-search?)
-      (state/get-editor-show-date-picker?)))
+  (or @*asset-uploading?
+      (state/get-editor-action)))
 
 (defn get-current-input-char
   [input]
@@ -1785,9 +1771,8 @@
 (defn close-autocomplete-if-outside
   [input]
   (when (and input
-             (or (state/get-editor-show-page-search?)
-                 (state/get-editor-show-page-search-hashtag?)
-                 (state/get-editor-show-block-search?))
+             (or (state/get-editor-action)
+                 (state/get-editor-show-page-search-hashtag?))
              (not (wrapped-by? input "[[" "]]")))
     (when (get-search-q)
       (let [value (gobj/get input "value")
@@ -1849,12 +1834,12 @@
     (when (= last-input-char (state/get-editor-command-trigger))
       (when (seq (get-matched-commands input))
         (reset! commands/*slash-caret-pos (cursor/get-caret-pos input))
-        (reset! commands/*show-commands true)))
+        (state/set-editor-show-commands!)))
 
     (if (= last-input-char commands/angle-bracket)
       (when (seq (get-matched-block-commands input))
         (reset! commands/*angle-bracket-caret-pos (cursor/get-caret-pos input))
-        (reset! commands/*show-block-commands true))
+        (state/set-editor-show-block-commands!))
       nil)))
 
 (defn block-on-chosen-handler
@@ -2569,7 +2554,7 @@
       (do
         (util/stop e)
         (reset! *slash-caret-pos nil)
-        (reset! *show-commands false)
+        (state/clear-editor-action!)
         (delete-and-update input (dec current-pos) current-pos))
 
       (and (> current-pos 1)
@@ -2577,7 +2562,7 @@
       (do
         (util/stop e)
         (reset! *angle-bracket-caret-pos nil)
-        (reset! *show-block-commands false)
+        (state/clear-editor-action!)
         (delete-and-update input (dec current-pos) current-pos))
 
       ;; pair
@@ -2635,9 +2620,7 @@
   (fn [e]
     (cond
       (state/editing?)
-      (when (and (not (state/get-editor-show-input))
-                 (not (state/get-editor-show-date-picker?))
-                 (not (state/get-editor-show-template-search?)))
+      (when-not (state/get-editor-action)
         (util/stop e)
         (indent-outdent (not (= :left direction))))
 
@@ -2759,97 +2742,97 @@
             blank-selected? (string/blank? (util/get-selected-text))
             is-processed? (util/event-is-composing? e true) ;; #3440
             non-enter-processed? (and is-processed? ;; #3251
-                                      (not= code keycode/enter-code))] ;; #3459
-        (when-not (or (state/get-editor-show-input) non-enter-processed?)
-          (cond
-            (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp"} k))
-                 (not (:editor/show-page-search? @state/state))
-                 (not (:editor/show-page-search-hashtag? @state/state))
-                 (wrapped-by? input "[[" "]]"))
-            (let [orig-pos (cursor/get-caret-pos input)
-                  value (gobj/get input "value")
-                  square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) "[[")
-                  pos (+ square-pos 2)
-                  _ (state/set-editor-last-pos! pos)
-                  pos (assoc orig-pos :pos pos)
-                  command-step (if (= \# (util/nth-safe value (dec square-pos)))
-                                 :editor/search-page-hashtag
-                                 :editor/search-page)]
-              (commands/handle-step [command-step])
-              (reset! commands/*slash-caret-pos pos))
-
-            (and blank-selected?
-                 (contains? keycode/left-square-brackets-keys k)
-                 (= (:key last-key-code) k)
-                 (> current-pos 0)
-                 (not (wrapped-by? input "[[" "]]")))
-            (do
-              (commands/handle-step [:editor/input "[[]]" {:backward-truncate-number 2
-                                                           :backward-pos 2}])
-              (commands/handle-step [:editor/search-page])
-              (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
-
-            (and blank-selected?
-                 (contains? keycode/left-paren-keys k)
-                 (= (:key last-key-code) k)
-                 (> current-pos 0)
-                 (not (wrapped-by? input "((" "))")))
-            (do
-              (commands/handle-step [:editor/input "(())" {:backward-truncate-number 2
-                                                           :backward-pos 2}])
-              (commands/handle-step [:editor/search-block :reference])
-              (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
-
-            (and (= "〈" c)
-                 (= "《" (util/nth-safe value (dec (dec current-pos))))
-                 (> current-pos 0))
-            (do
-              (commands/handle-step [:editor/input commands/angle-bracket {:last-pattern "《〈"
-                                                                           :backward-pos 0}])
-              (reset! commands/*angle-bracket-caret-pos (cursor/get-caret-pos input))
-              (reset! commands/*show-block-commands true))
-
-            ;; Exit autocomplete if user inputs two consecutive spaces
-            (and @*show-commands
-                 (= c (util/nth-safe value (dec (dec current-pos))) " "))
-            (reset! *show-commands false)
-
-            (and (= c " ")
-                 (or (= (util/nth-safe value (dec (dec current-pos))) "#")
-                     (not (state/get-editor-show-page-search?))
-                     (and (state/get-editor-show-page-search?)
-                          (not= (util/nth-safe value current-pos) "]"))))
-            (state/set-editor-show-page-search-hashtag! false)
-
-            (and @*show-commands (not= k (state/get-editor-command-trigger)))
-            (let [matched-commands (get-matched-commands input)]
-              (if (seq matched-commands)
+                                      (not= code keycode/enter-code)) ;; #3459
+            editor-action (state/get-editor-action)]
+        (cond
+          (and (= :commands (state/get-editor-action)) (not= k (state/get-editor-command-trigger)))
+          (let [matched-commands (get-matched-commands input)]
+            (if (seq matched-commands)
+              (do
+                (state/set-editor-show-commands!)
+                (reset! commands/*matched-commands matched-commands))
+              (state/clear-editor-action!)))
+
+          (and (= :block-commands editor-action) (not= key-code 188)) ; not <
+          (let [matched-block-commands (get-matched-block-commands input)]
+            (if (seq matched-block-commands)
+              (cond
+                (= key-code 9)       ;tab
                 (do
-                  (reset! *show-commands true)
-                  (reset! commands/*matched-commands matched-commands))
-                (reset! *show-commands false)))
-
-            (and @*show-block-commands (not= key-code 188)) ; not <
-            (let [matched-block-commands (get-matched-block-commands input)]
-              (if (seq matched-block-commands)
-                (cond
-                  (= key-code 9)       ;tab
-                  (when @*show-block-commands
-                    (util/stop e)
-                    (insert-command! input-id
-                                     (last (first matched-block-commands))
-                                     format
-                                     {:last-pattern commands/angle-bracket}))
-
-                  :else
-                  (reset! commands/*matched-block-commands matched-block-commands))
-                (reset! *show-block-commands false)))
-
-            (nil? @search-timeout)
-            (close-autocomplete-if-outside input)
+                  (util/stop e)
+                  (insert-command! input-id
+                                   (last (first matched-block-commands))
+                                   format
+                                   {:last-pattern commands/angle-bracket}))
+
+                :else
+                (reset! commands/*matched-block-commands matched-block-commands))
+              (state/clear-editor-action!)))
+
+          (and (contains? #{:commands :block-commands} (state/get-editor-action))
+               (= c (util/nth-safe value (dec (dec current-pos))) " "))
+          (state/clear-editor-action!)
+
+          (and (= c " ")
+               (or (= (util/nth-safe value (dec (dec current-pos))) "#")
+                   (not (state/get-editor-show-page-search?))
+                   (and (state/get-editor-show-page-search?)
+                        (not= (util/nth-safe value current-pos) "]"))))
+          (state/set-editor-show-page-search-hashtag! false)
 
-            :else
-            nil))
+          :else
+          (when (and (not editor-action) (not non-enter-processed?))
+            (cond
+              (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp"} k))
+                   (wrapped-by? input "[[" "]]"))
+              (let [orig-pos (cursor/get-caret-pos input)
+                    value (gobj/get input "value")
+                    square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) "[[")
+                    pos (+ square-pos 2)
+                    _ (state/set-editor-last-pos! pos)
+                    pos (assoc orig-pos :pos pos)
+                    command-step (if (= \# (util/nth-safe value (dec square-pos)))
+                                   :editor/search-page-hashtag
+                                   :editor/search-page)]
+                (commands/handle-step [command-step])
+                (reset! commands/*slash-caret-pos pos))
+
+              (and blank-selected?
+                   (contains? keycode/left-square-brackets-keys k)
+                   (= (:key last-key-code) k)
+                   (> current-pos 0)
+                   (not (wrapped-by? input "[[" "]]")))
+              (do
+                (commands/handle-step [:editor/input "[[]]" {:backward-truncate-number 2
+                                                             :backward-pos 2}])
+                (commands/handle-step [:editor/search-page])
+                (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
+
+              (and blank-selected?
+                   (contains? keycode/left-paren-keys k)
+                   (= (:key last-key-code) k)
+                   (> current-pos 0)
+                   (not (wrapped-by? input "((" "))")))
+              (do
+                (commands/handle-step [:editor/input "(())" {:backward-truncate-number 2
+                                                             :backward-pos 2}])
+                (commands/handle-step [:editor/search-block :reference])
+                (reset! commands/*slash-caret-pos (cursor/get-caret-pos input)))
+
+              (and (= "〈" c)
+                   (= "《" (util/nth-safe value (dec (dec current-pos))))
+                   (> current-pos 0))
+              (do
+                (commands/handle-step [:editor/input commands/angle-bracket {:last-pattern "《〈"
+                                                                             :backward-pos 0}])
+                (reset! commands/*angle-bracket-caret-pos (cursor/get-caret-pos input))
+                (state/set-editor-show-block-commands!))
+
+              (nil? @search-timeout)
+              (close-autocomplete-if-outside input)
+
+              :else
+              nil)))
         (when-not (or (= k "Shift") is-processed?)
           (state/set-last-key-code! {:key-code key-code
                                      :code code
@@ -2866,7 +2849,7 @@
 (defn editor-on-change!
   [block id search-timeout]
   (fn [e]
-    (if (state/sub :editor/show-block-search?)
+    (if (= :block-search (state/sub :editor/action))
       (let [timeout 300]
         (when @search-timeout
           (js/clearTimeout @search-timeout))

+ 6 - 4
src/main/frontend/handler/page.cljs

@@ -691,15 +691,17 @@
   [input id _q pos format]
   (let [current-pos (cursor/pos input)
         edit-content (state/sub [:editor/content id])
+        action (state/get-editor-action)
+        hashtag? (= action :page-search-hashtag)
         q (or
            @editor-handler/*selected-text
-           (when (state/sub :editor/show-page-search-hashtag?)
+           (when hashtag?
              (gp-util/safe-subs edit-content pos current-pos))
            (when (> (count edit-content) current-pos)
              (gp-util/safe-subs edit-content pos current-pos)))]
-    (if (state/sub :editor/show-page-search-hashtag?)
+    (if hashtag?
       (fn [chosen _click?]
-        (state/set-editor-show-page-search! false)
+        (state/clear-editor-action!)
         (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
               chosen (if (string/starts-with? chosen "New page: ") ;; FIXME: What if a page named "New page: XXX"?
                        (subs chosen 10)
@@ -721,7 +723,7 @@
                                            :end-pattern (when wrapped? "]]")
                                            :forward-pos forward-pos})))
       (fn [chosen _click?]
-        (state/set-editor-show-page-search! false)
+        (state/clear-editor-action!)
         (let [chosen (if (string/starts-with? chosen "New page: ")
                        (subs chosen 10)
                        chosen)

+ 44 - 27
src/main/frontend/state.cljs

@@ -97,12 +97,9 @@
      :config                                {}
      :block/component-editing-mode?         false
      :editor/draw-mode?                     false
-     :editor/show-page-search?              false
-     :editor/show-page-search-hashtag?      false
-     :editor/show-date-picker?              false
+     :editor/action                         nil
+     :editor/action-data                    nil
      ;; With label or other data
-     :editor/show-input                     nil
-     :editor/show-zotero                    false
      :editor/last-saved-cursor              nil
      :editor/editing?                       nil
      :editor/in-composition?                false
@@ -589,63 +586,83 @@
   [value]
   (set-state! :search/mode value))
 
+(defn set-editor-action!
+  [value]
+  (set-state! :editor/action value))
+
+(defn set-editor-action-data!
+  [value]
+  (set-state! :editor/action-data value))
+
+(defn get-editor-action
+  []
+  (:editor/action @state))
+
 (defn set-editor-show-page-search!
   [value]
-  (set-state! :editor/show-page-search? value))
+  (set-editor-action! (when value :page-search)))
 
 (defn get-editor-show-page-search?
   []
-  (get @state :editor/show-page-search?))
+  (= (get-editor-action) :page-search))
 
 (defn set-editor-show-page-search-hashtag!
   [value]
-  (set-state! :editor/show-page-search? value)
-  (set-state! :editor/show-page-search-hashtag? value))
+  (set-editor-action! (when value :page-search-hashtag)))
+
 (defn get-editor-show-page-search-hashtag?
   []
-  (get @state :editor/show-page-search-hashtag?))
+  (= (get-editor-action) :page-search-hashtag))
 (defn set-editor-show-block-search!
   [value]
-  (set-state! :editor/show-block-search? value))
+  (set-editor-action! (when value :block-search)))
 (defn get-editor-show-block-search?
   []
-  (get @state :editor/show-block-search?))
+  (= (get-editor-action) :block-search))
 (defn set-editor-show-template-search!
   [value]
-  (set-state! :editor/show-template-search? value))
+  (set-editor-action! (when value :template-search)))
 (defn get-editor-show-template-search?
   []
-  (get @state :editor/show-template-search?))
+  (= (get-editor-action) :template-search))
 (defn set-editor-show-date-picker!
   [value]
-  (set-state! :editor/show-date-picker? value))
+  (set-editor-action! (when value :datepicker)))
 (defn get-editor-show-date-picker?
   []
-  (get @state :editor/show-date-picker?))
+  (= (get-editor-action) :datepicker))
 (defn set-editor-show-input!
   [value]
-  (set-state! :editor/show-input value))
+  (if value
+    (do
+      (set-editor-action-data! value)
+      (set-editor-action! :input))
+    (do
+      (set-editor-action! nil)
+      (set-editor-action-data! nil))))
 (defn get-editor-show-input
   []
-  (get @state :editor/show-input))
+  (when (= (get-editor-action) :input)
+    (get @state :editor/action-data)))
+(defn set-editor-show-commands!
+  []
+  (when-not (get-editor-action) (set-editor-action! :commands)))
+(defn set-editor-show-block-commands!
+  []
+  (when-not (get-editor-action) (set-editor-action! :block-commands)))
+
 
 
 (defn set-editor-show-zotero!
   [value]
   (set-state! :editor/show-zotero value))
 
-;; TODO: refactor, use one state
-(defn clear-editor-show-state!
+(defn clear-editor-action!
   []
   (swap! state (fn [state]
                  (assoc state
-                        :editor/show-input nil
-                        :editor/show-zotero false
-                        :editor/show-date-picker? false
-                        :editor/show-block-search? false
-                        :editor/show-template-search? false
-                        :editor/show-page-search? false
-                        :editor/show-page-search-hashtag? false))))
+                        :editor/action nil
+                        :editor/action-data nil))))
 
 (defn set-edit-input-id!
   [input-id]

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

@@ -65,10 +65,7 @@
                             (plugin-handler/hook-plugin-editor :input-selection-end (bean/->js e)))))))
                 state)}
   [{:keys [on-change] :as props}]
-  (let [skip-composition? (or
-                           (state/sub :editor/show-page-search?)
-                           (state/sub :editor/show-block-search?)
-                           (state/sub :editor/show-template-search?))
+  (let [skip-composition? (state/sub :editor/action)
         on-composition (fn [e]
                          (if skip-composition?
                            (on-change e)