|
|
@@ -3,25 +3,19 @@
|
|
|
[frontend.components.svg :as svg]
|
|
|
[frontend.config :as config]
|
|
|
[frontend.handler.editor :as editor-handler :refer [get-state]]
|
|
|
+ [frontend.handler.editor.lifecycle :as lifecycle]
|
|
|
[frontend.util :as util :refer-macros [profile]]
|
|
|
- [frontend.handler.file :as file]
|
|
|
[frontend.handler.block :as block-handler]
|
|
|
[frontend.handler.page :as page-handler]
|
|
|
- [frontend.handler.editor.keyboards :as keyboards-handler]
|
|
|
[frontend.components.datetime :as datetime-comp]
|
|
|
- [promesa.core :as p]
|
|
|
[frontend.state :as state]
|
|
|
[frontend.mixins :as mixins]
|
|
|
[frontend.ui :as ui]
|
|
|
[frontend.db :as db]
|
|
|
- [frontend.config :as config]
|
|
|
- [frontend.handler.web.nfs :as nfs]
|
|
|
[dommy.core :as d]
|
|
|
[goog.object :as gobj]
|
|
|
[goog.dom :as gdom]
|
|
|
[clojure.string :as string]
|
|
|
- [clojure.set :as set]
|
|
|
- [cljs.core.match :refer-macros [match]]
|
|
|
[frontend.commands :as commands
|
|
|
:refer [*show-commands
|
|
|
*matched-commands
|
|
|
@@ -29,9 +23,6 @@
|
|
|
*angle-bracket-caret-pos
|
|
|
*matched-block-commands
|
|
|
*show-block-commands]]
|
|
|
- [medley.core :as medley]
|
|
|
- [cljs-drag-n-drop.core :as dnd]
|
|
|
- [frontend.text :as text]
|
|
|
["/frontend/utils" :as utils]))
|
|
|
|
|
|
(rum/defc commands < rum/reactive
|
|
|
@@ -58,7 +49,7 @@
|
|
|
(editor-handler/insert-command! id command-steps
|
|
|
format
|
|
|
{:restore? restore-slash?})))
|
|
|
- :class "black"}))))
|
|
|
+ :class "black"}))))
|
|
|
|
|
|
(rum/defc block-commands < rum/reactive
|
|
|
[id format]
|
|
|
@@ -71,7 +62,7 @@
|
|
|
(editor-handler/insert-command! id (get (into {} matched) chosen)
|
|
|
format
|
|
|
{:last-pattern commands/angle-bracket}))
|
|
|
- :class "black"}))))
|
|
|
+ :class "black"}))))
|
|
|
|
|
|
(rum/defc page-search < rum/reactive
|
|
|
{:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
|
|
|
@@ -90,92 +81,44 @@
|
|
|
(when (> (count edit-content) current-pos)
|
|
|
(util/safe-subs edit-content pos current-pos)))
|
|
|
matched-pages (when-not (string/blank? q)
|
|
|
- (editor-handler/get-matched-pages q))
|
|
|
- chosen-handler (if (state/sub :editor/show-page-search-hashtag?)
|
|
|
- (fn [chosen _click?]
|
|
|
- (state/set-editor-show-page-search! false)
|
|
|
- (let [chosen (if (re-find #"\s+" chosen)
|
|
|
- (util/format "[[%s]]" chosen)
|
|
|
- chosen)]
|
|
|
- (editor-handler/insert-command! id
|
|
|
- (str "#" chosen)
|
|
|
- format
|
|
|
- {:last-pattern (str "#" (if @editor-handler/*selected-text "" q))})))
|
|
|
- (fn [chosen _click?]
|
|
|
- (state/set-editor-show-page-search! false)
|
|
|
- (let [page-ref-text (page-handler/get-page-ref-text chosen)]
|
|
|
- (editor-handler/insert-command! id
|
|
|
- page-ref-text
|
|
|
- format
|
|
|
- {:last-pattern (str "[[" (if @editor-handler/*selected-text "" q))
|
|
|
- :postfix-fn (fn [s] (util/replace-first "]]" s ""))}))))
|
|
|
- non-exist-page-handler (fn [_state]
|
|
|
- (state/set-editor-show-page-search! false)
|
|
|
- (if (state/org-mode-file-link? (state/get-current-repo))
|
|
|
- (let [page-ref-text (page-handler/get-page-ref-text q)
|
|
|
- value (gobj/get input "value")
|
|
|
- old-page-ref (util/format "[[%s]]" q)
|
|
|
- new-value (string/replace value
|
|
|
- old-page-ref
|
|
|
- page-ref-text)]
|
|
|
- (state/set-edit-content! id new-value)
|
|
|
- (let [new-pos (+ current-pos
|
|
|
- (- (count page-ref-text)
|
|
|
- (count old-page-ref))
|
|
|
- 2)]
|
|
|
- (util/move-cursor-to input new-pos)))
|
|
|
- (util/cursor-move-forward input 2)))]
|
|
|
+ (editor-handler/get-matched-pages q))]
|
|
|
(ui/auto-complete
|
|
|
matched-pages
|
|
|
- {:on-chosen chosen-handler
|
|
|
- :on-enter non-exist-page-handler
|
|
|
+ {: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)
|
|
|
:empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a page"]
|
|
|
- :class "black"}))))))
|
|
|
+ :class "black"}))))))
|
|
|
|
|
|
-(rum/defc block-search < rum/reactive
|
|
|
- {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
|
|
|
- [id format]
|
|
|
+(rum/defcs block-search < rum/reactive
|
|
|
+ {:will-unmount (fn [state]
|
|
|
+ (reset! editor-handler/*selected-text nil)
|
|
|
+ (state/clear-search-result!)
|
|
|
+ state)}
|
|
|
+ [state id format]
|
|
|
(when (state/sub :editor/show-block-search?)
|
|
|
(let [pos (:editor/last-saved-cursor @state/state)
|
|
|
- input (gdom/getElement id)]
|
|
|
+ input (gdom/getElement id)
|
|
|
+ [id format] (:rum/args state)
|
|
|
+ current-pos (:pos (util/get-caret-pos input))
|
|
|
+ edit-content (state/sub [:editor/content id])
|
|
|
+ edit-block (state/get-edit-block)
|
|
|
+ q (or
|
|
|
+ @editor-handler/*selected-text
|
|
|
+ (when (> (count edit-content) current-pos)
|
|
|
+ (subs edit-content pos current-pos)))
|
|
|
+ matched-blocks (when-not (string/blank? q)
|
|
|
+ (editor-handler/get-matched-blocks q (:block/uuid edit-block)))]
|
|
|
(when input
|
|
|
- (let [current-pos (:pos (util/get-caret-pos input))
|
|
|
- edit-content (state/sub [:editor/content id])
|
|
|
- q (or
|
|
|
- @editor-handler/*selected-text
|
|
|
- (when (> (count edit-content) current-pos)
|
|
|
- (subs edit-content pos current-pos)))
|
|
|
- matched-blocks (when-not (string/blank? q)
|
|
|
- (editor-handler/get-matched-blocks q))
|
|
|
- chosen-handler (fn [chosen _click?]
|
|
|
- (state/set-editor-show-block-search! false)
|
|
|
- (let [uuid-string (str (:block/uuid chosen))]
|
|
|
-
|
|
|
- ;; block reference
|
|
|
- (editor-handler/insert-command! id
|
|
|
- (util/format "((%s))" uuid-string)
|
|
|
- format
|
|
|
- {:last-pattern (str "((" (if @editor-handler/*selected-text "" q))
|
|
|
- :postfix-fn (fn [s] (util/replace-first "))" s ""))})
|
|
|
-
|
|
|
- ;; Save it so it'll be parsed correctly in the future
|
|
|
- (editor-handler/set-block-property! (:block/uuid chosen)
|
|
|
- "ID"
|
|
|
- uuid-string)
|
|
|
-
|
|
|
- (when-let [input (gdom/getElement id)]
|
|
|
- (.focus input))))
|
|
|
- non-exist-block-handler (fn [_state]
|
|
|
- (state/set-editor-show-block-search! false)
|
|
|
- (util/cursor-move-forward input 2))]
|
|
|
+ (let [chosen-handler (editor-handler/block-on-chosen-handler input id q format)
|
|
|
+ non-exist-block-handler (editor-handler/block-non-exist-handler input)]
|
|
|
(ui/auto-complete
|
|
|
matched-blocks
|
|
|
- {:on-chosen chosen-handler
|
|
|
- :on-enter non-exist-block-handler
|
|
|
- :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
|
|
|
+ {:on-chosen chosen-handler
|
|
|
+ :on-enter non-exist-block-handler
|
|
|
+ :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a block"]
|
|
|
:item-render (fn [{:block/keys [content]}]
|
|
|
(subs content 0 64))
|
|
|
- :class "black"}))))))
|
|
|
+ :class "black"}))))))
|
|
|
|
|
|
(rum/defc template-search < rum/reactive
|
|
|
{:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
|
|
|
@@ -192,43 +135,16 @@
|
|
|
(subs edit-content pos current-pos))
|
|
|
"")
|
|
|
matched-templates (editor-handler/get-matched-templates q)
|
|
|
- chosen-handler (fn [[template db-id] _click?]
|
|
|
- (if-let [block (db/entity db-id)]
|
|
|
- (let [new-level (:block/level edit-block)
|
|
|
- template-parent-level (:block/level block)
|
|
|
- pattern (config/get-block-pattern format)
|
|
|
- content
|
|
|
- (block-handler/get-block-full-content
|
|
|
- (state/get-current-repo)
|
|
|
- (:block/uuid block)
|
|
|
- (fn [{:block/keys [level content properties] :as block}]
|
|
|
- (let [new-level (+ new-level (- level template-parent-level))
|
|
|
- properties' (dissoc (into {} properties) "id" "custom_id" "template")]
|
|
|
- (-> content
|
|
|
- (string/replace-first (apply str (repeat level pattern))
|
|
|
- (apply str (repeat new-level pattern)))
|
|
|
- text/remove-properties!
|
|
|
- (text/rejoin-properties properties')))))
|
|
|
- content (if (string/includes? (string/trim edit-content) "\n")
|
|
|
- content
|
|
|
- (text/remove-level-spaces content format))]
|
|
|
- (state/set-editor-show-template-search! false)
|
|
|
- (editor-handler/insert-command! id
|
|
|
- content
|
|
|
- format
|
|
|
- {})))
|
|
|
- (when-let [input (gdom/getElement id)]
|
|
|
- (.focus input)))
|
|
|
non-exist-handler (fn [_state]
|
|
|
(state/set-editor-show-template-search! false))]
|
|
|
(ui/auto-complete
|
|
|
matched-templates
|
|
|
- {:on-chosen chosen-handler
|
|
|
- :on-enter non-exist-handler
|
|
|
- :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a template"]
|
|
|
+ {:on-chosen (editor-handler/template-on-chosen-handler input id q format edit-block edit-content)
|
|
|
+ :on-enter non-exist-handler
|
|
|
+ :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a template"]
|
|
|
:item-render (fn [[template _block-db-id]]
|
|
|
template)
|
|
|
- :class "black"}))))))
|
|
|
+ :class "black"}))))))
|
|
|
|
|
|
(rum/defc mobile-bar < rum/reactive
|
|
|
[parent-state parent-id]
|
|
|
@@ -248,22 +164,28 @@
|
|
|
[:button.bottom-action
|
|
|
{:on-click #(commands/simple-insert! parent-id "\n" {})}
|
|
|
svg/multi-line-input]
|
|
|
+ [:button.bottom-action
|
|
|
+ {:on-click #(commands/insert-before! parent-id "TODO " {})}
|
|
|
+ svg/checkbox]
|
|
|
[:button.font-extrabold.bottom-action.-mt-1
|
|
|
{:on-click #(commands/simple-insert!
|
|
|
parent-id "[[]]"
|
|
|
{:backward-pos 2
|
|
|
- :check-fn (fn [_ _ new-pos]
|
|
|
- (reset! commands/*slash-caret-pos new-pos)
|
|
|
- (commands/handle-step [:editor/search-page]))})}
|
|
|
+ :check-fn (fn [_ _ new-pos]
|
|
|
+ (reset! commands/*slash-caret-pos new-pos)
|
|
|
+ (commands/handle-step [:editor/search-page]))})}
|
|
|
"[[]]"]
|
|
|
[:button.font-extrabold.bottom-action.-mt-1
|
|
|
{:on-click #(commands/simple-insert!
|
|
|
parent-id "(())"
|
|
|
{:backward-pos 2
|
|
|
- :check-fn (fn [_ _ new-pos]
|
|
|
- (reset! commands/*slash-caret-pos new-pos)
|
|
|
- (commands/handle-step [:editor/search-block]))})}
|
|
|
- "(())"]])
|
|
|
+ :check-fn (fn [_ _ new-pos]
|
|
|
+ (reset! commands/*slash-caret-pos new-pos)
|
|
|
+ (commands/handle-step [:editor/search-block]))})}
|
|
|
+ "(())"]
|
|
|
+ [:button.font-extrabold.bottom-action.-mt-1
|
|
|
+ {:on-click #(commands/simple-insert! parent-id "/" {})}
|
|
|
+ "/"]])
|
|
|
|
|
|
(rum/defcs input < rum/reactive
|
|
|
(rum/local {} ::input-value)
|
|
|
@@ -306,11 +228,11 @@
|
|
|
[: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)))
|
|
|
+ {: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))
|
|
|
@@ -357,31 +279,31 @@
|
|
|
(when-let [pos (rum/react pos)]
|
|
|
(ui/css-transition
|
|
|
{:class-names "fade"
|
|
|
- :timeout {:enter 500
|
|
|
- :exit 300}}
|
|
|
+ :timeout {:enter 500
|
|
|
+ :exit 300}}
|
|
|
(absolute-modal cp set-default-width? pos)))))
|
|
|
|
|
|
(rum/defc image-uploader < rum/reactive
|
|
|
{:did-mount (fn [state]
|
|
|
(let [[id format] (:rum/args state)]
|
|
|
- (add-watch editor-handler/*image-pending-file ::pending-image
|
|
|
+ (add-watch editor-handler/*asset-pending-file ::pending-asset
|
|
|
(fn [_ _ _ f]
|
|
|
(reset! *slash-caret-pos (util/get-caret-pos (gdom/getElement id)))
|
|
|
- (editor-handler/upload-image id #js[f] format editor-handler/*image-uploading? true))))
|
|
|
+ (editor-handler/upload-asset id #js[f] format editor-handler/*asset-uploading? true))))
|
|
|
state)
|
|
|
:will-unmount (fn [state]
|
|
|
- (remove-watch editor-handler/*image-pending-file ::pending-image))}
|
|
|
+ (remove-watch editor-handler/*asset-pending-file ::pending-asset))}
|
|
|
[id format]
|
|
|
[:div.image-uploader
|
|
|
[:input
|
|
|
- {:id "upload-file"
|
|
|
- :type "file"
|
|
|
+ {:id "upload-file"
|
|
|
+ :type "file"
|
|
|
:on-change (fn [e]
|
|
|
(let [files (.-files (.-target e))]
|
|
|
- (editor-handler/upload-image id files format editor-handler/*image-uploading? false)))
|
|
|
- :hidden true}]
|
|
|
- (when-let [uploading? (util/react editor-handler/*image-uploading?)]
|
|
|
- (let [processing (util/react editor-handler/*image-uploading-process)]
|
|
|
+ (editor-handler/upload-asset id files format editor-handler/*asset-uploading? false)))
|
|
|
+ :hidden true}]
|
|
|
+ (when-let [uploading? (util/react editor-handler/*asset-uploading?)]
|
|
|
+ (let [processing (util/react editor-handler/*asset-uploading-process)]
|
|
|
(transition-cp
|
|
|
[:div.flex.flex-row.align-center.rounded-md.shadow-sm.bg-base-2.px-1.py-1
|
|
|
(ui/loading
|
|
|
@@ -389,373 +311,58 @@
|
|
|
false
|
|
|
*slash-caret-pos)))])
|
|
|
|
|
|
-(rum/defcs box < rum/reactive
|
|
|
- (mixins/event-mixin
|
|
|
- (fn [state]
|
|
|
- (let [{:keys [id format block]} (get-state state)
|
|
|
- input-id id
|
|
|
- input (gdom/getElement input-id)
|
|
|
- repo (:block/repo block)]
|
|
|
- (mixins/on-key-down
|
|
|
- state
|
|
|
- {;; enter
|
|
|
- 13 (fn [state e]
|
|
|
- (when (and (not (gobj/get e "ctrlKey"))
|
|
|
- (not (gobj/get e "metaKey"))
|
|
|
- (not (editor-handler/in-auto-complete? input)))
|
|
|
- (let [{:keys [block config]} (get-state state)]
|
|
|
- (when (and block
|
|
|
- (not (:ref? config))
|
|
|
- (not (:custom-query? config))) ; in reference section
|
|
|
- (let [content (state/get-edit-content)]
|
|
|
- (if (and
|
|
|
- (> (:block/level block) 2)
|
|
|
- (string/blank? content))
|
|
|
- (do
|
|
|
- (util/stop e)
|
|
|
- (editor-handler/adjust-block-level! state :left))
|
|
|
- (let [shortcut (state/get-new-block-shortcut)
|
|
|
- insert? (cond
|
|
|
- config/mobile?
|
|
|
- true
|
|
|
-
|
|
|
- (and (= shortcut "alt+enter") (not (gobj/get e "altKey")))
|
|
|
- false
|
|
|
-
|
|
|
- (gobj/get e "shiftKey")
|
|
|
- false
|
|
|
-
|
|
|
- :else
|
|
|
- true)]
|
|
|
- (when (and
|
|
|
- insert?
|
|
|
- (not (editor-handler/in-auto-complete? input)))
|
|
|
- (util/stop e)
|
|
|
- (profile
|
|
|
- "Insert block"
|
|
|
- (editor-handler/insert-new-block! state))))))))))
|
|
|
- ;; up
|
|
|
- 38 (fn [state e]
|
|
|
- (when (and
|
|
|
- (not (gobj/get e "ctrlKey"))
|
|
|
- (not (gobj/get e "metaKey"))
|
|
|
- (not (editor-handler/in-auto-complete? input)))
|
|
|
- (editor-handler/on-up-down state e true)))
|
|
|
- ;; down
|
|
|
- 40 (fn [state e]
|
|
|
- (when (and
|
|
|
- (not (gobj/get e "ctrlKey"))
|
|
|
- (not (gobj/get e "metaKey"))
|
|
|
- (not (editor-handler/in-auto-complete? input)))
|
|
|
- (editor-handler/on-up-down state e false)))
|
|
|
- ;; backspace
|
|
|
- 8 (fn [state e]
|
|
|
- (let [node (gdom/getElement input-id)
|
|
|
- current-pos (:pos (util/get-caret-pos node))
|
|
|
- value (gobj/get node "value")
|
|
|
- deleted (and (> current-pos 0)
|
|
|
- (util/nth-safe value (dec current-pos)))
|
|
|
- selected-start (gobj/get node "selectionStart")
|
|
|
- selected-end (gobj/get node "selectionEnd")
|
|
|
- block-id (:block-id (first (:rum/args state)))
|
|
|
- page (state/get-current-page)]
|
|
|
- (cond
|
|
|
- (not= selected-start selected-end)
|
|
|
- nil
|
|
|
-
|
|
|
- (and (zero? current-pos)
|
|
|
- ;; not the top block in a block page
|
|
|
- (not (and page
|
|
|
- (util/uuid-string? page)
|
|
|
- (= (medley/uuid page) block-id))))
|
|
|
- (editor-handler/delete-block! state repo e)
|
|
|
-
|
|
|
- (and (> current-pos 1)
|
|
|
- (= (util/nth-safe value (dec current-pos)) commands/slash))
|
|
|
- (do
|
|
|
- (reset! *slash-caret-pos nil)
|
|
|
- (reset! *show-commands false))
|
|
|
+(defn- set-up-key-down!
|
|
|
+ [repo state input input-id format]
|
|
|
+ (mixins/on-key-down
|
|
|
+ state
|
|
|
+ {;; enter
|
|
|
+ 13 (editor-handler/keydown-enter-handler state input)
|
|
|
+ ;; up
|
|
|
+ 38 (editor-handler/keydown-up-down-handler input true)
|
|
|
+ ;; down
|
|
|
+ 40 (editor-handler/keydown-up-down-handler input false)
|
|
|
+ ;; backspace
|
|
|
+ 8 (editor-handler/keydown-backspace-handler repo input input-id)
|
|
|
+ ;; tab
|
|
|
+ 9 (editor-handler/keydown-tab-handler input input-id)}
|
|
|
+ {:not-matched-handler (editor-handler/keydown-not-matched-handler input input-id format)}))
|
|
|
+
|
|
|
+(defn- set-up-key-up!
|
|
|
+ [state input input-id search-timeout]
|
|
|
+ (mixins/on-key-up
|
|
|
+ state
|
|
|
+ {}
|
|
|
+ (editor-handler/keyup-handler state input input-id search-timeout)))
|
|
|
+
|
|
|
+(def search-timeout (atom nil))
|
|
|
+
|
|
|
+(defn- setup-key-listener!
|
|
|
+ [state]
|
|
|
+ (let [{:keys [id format block]} (get-state state)
|
|
|
+ input-id id
|
|
|
+ input (gdom/getElement input-id)
|
|
|
+ repo (:block/repo block)]
|
|
|
+ (set-up-key-down! repo state input input-id format)
|
|
|
+ (set-up-key-up! state input input-id search-timeout)))
|
|
|
|
|
|
- (and (> current-pos 1)
|
|
|
- (= (util/nth-safe value (dec current-pos)) commands/angle-bracket))
|
|
|
- (do
|
|
|
- (reset! *angle-bracket-caret-pos nil)
|
|
|
- (reset! *show-block-commands false))
|
|
|
-
|
|
|
- ;; pair
|
|
|
- (and
|
|
|
- deleted
|
|
|
- (contains?
|
|
|
- (set (keys editor-handler/delete-map))
|
|
|
- deleted)
|
|
|
- (>= (count value) (inc current-pos))
|
|
|
- (= (util/nth-safe value current-pos)
|
|
|
- (get editor-handler/delete-map deleted)))
|
|
|
-
|
|
|
- (do
|
|
|
- (util/stop e)
|
|
|
- (commands/delete-pair! id)
|
|
|
- (cond
|
|
|
- (and (= deleted "[") (state/get-editor-show-page-search?))
|
|
|
- (state/set-editor-show-page-search! false)
|
|
|
-
|
|
|
- (and (= deleted "(") (state/get-editor-show-block-search?))
|
|
|
- (state/set-editor-show-block-search! false)
|
|
|
-
|
|
|
- :else
|
|
|
- nil))
|
|
|
-
|
|
|
- ;; deleting hashtag
|
|
|
- (and (= deleted "#") (state/get-editor-show-page-search-hashtag?))
|
|
|
- (state/set-editor-show-page-search-hashtag! false)
|
|
|
-
|
|
|
- :else
|
|
|
- nil)))
|
|
|
- ;; tab
|
|
|
- 9 (fn [state e]
|
|
|
- (let [input-id (state/get-edit-input-id)
|
|
|
- input (and input-id (gdom/getElement id))
|
|
|
- pos (and input (:pos (util/get-caret-pos input)))]
|
|
|
- (when (and (not (state/get-editor-show-input))
|
|
|
- (not (state/get-editor-show-date-picker?))
|
|
|
- (not (state/get-editor-show-template-search?)))
|
|
|
- (util/stop e)
|
|
|
- (let [direction (if (gobj/get e "shiftKey") ; shift+tab move to left
|
|
|
- :left
|
|
|
- :right)]
|
|
|
- (p/let [_ (editor-handler/adjust-block-level! state direction)]
|
|
|
- (and input pos (js/setTimeout #(when-let [input (gdom/getElement input-id)]
|
|
|
- (util/move-cursor-to input pos))
|
|
|
- 0)))))))}
|
|
|
- {:not-matched-handler
|
|
|
- (fn [e key-code]
|
|
|
- (let [key (gobj/get e "key")
|
|
|
- value (gobj/get input "value")
|
|
|
- ctrlKey (gobj/get e "ctrlKey")
|
|
|
- metaKey (gobj/get e "metaKey")
|
|
|
- pos (util/get-input-pos input)]
|
|
|
- (cond
|
|
|
- (or ctrlKey metaKey)
|
|
|
- nil
|
|
|
-
|
|
|
- (or
|
|
|
- (and (= key "#")
|
|
|
- (and
|
|
|
- (> pos 0)
|
|
|
- (= "#" (util/nth-safe value (dec pos)))))
|
|
|
- (and (= key " ")
|
|
|
- (state/get-editor-show-page-search-hashtag?)))
|
|
|
- (state/set-editor-show-page-search-hashtag! false)
|
|
|
-
|
|
|
- (or
|
|
|
- (editor-handler/surround-by? input "#" " ")
|
|
|
- (editor-handler/surround-by? input "#" :end)
|
|
|
- (= key "#"))
|
|
|
- (do
|
|
|
- (commands/handle-step [:editor/search-page-hashtag])
|
|
|
- (state/set-last-pos! (:pos (util/get-caret-pos input)))
|
|
|
- (reset! commands/*slash-caret-pos (util/get-caret-pos input)))
|
|
|
-
|
|
|
- (and
|
|
|
- (= key " ")
|
|
|
- (state/get-editor-show-page-search-hashtag?))
|
|
|
- (state/set-editor-show-page-search-hashtag! false)
|
|
|
-
|
|
|
- (and
|
|
|
- (contains? (set/difference (set (keys editor-handler/reversed-autopair-map))
|
|
|
- #{"`"})
|
|
|
- key)
|
|
|
- (= (editor-handler/get-current-input-char input) key))
|
|
|
- (do
|
|
|
- (util/stop e)
|
|
|
- (util/cursor-move-forward input 1))
|
|
|
-
|
|
|
- (contains? (set (keys editor-handler/autopair-map)) key)
|
|
|
- (do
|
|
|
- (util/stop e)
|
|
|
- (editor-handler/autopair input-id key format nil)
|
|
|
- (cond
|
|
|
- (editor-handler/surround-by? input "[[" "]]")
|
|
|
- (do
|
|
|
- (commands/handle-step [:editor/search-page])
|
|
|
- (reset! commands/*slash-caret-pos (util/get-caret-pos input)))
|
|
|
- (editor-handler/surround-by? input "((" "))")
|
|
|
- (do
|
|
|
- (commands/handle-step [:editor/search-block :reference])
|
|
|
- (reset! commands/*slash-caret-pos (util/get-caret-pos input)))
|
|
|
- :else
|
|
|
- nil))
|
|
|
-
|
|
|
- (let [sym "$"]
|
|
|
- (and (= key sym)
|
|
|
- (>= (count value) 1)
|
|
|
- (> pos 0)
|
|
|
- (= (nth value (dec pos)) sym)
|
|
|
- (if (> (count value) pos)
|
|
|
- (not= (nth value pos) sym)
|
|
|
- true)))
|
|
|
- (commands/simple-insert! input-id "$$" {:backward-pos 2})
|
|
|
-
|
|
|
- (let [sym "^"]
|
|
|
- (and (= key sym)
|
|
|
- (>= (count value) 1)
|
|
|
- (> pos 0)
|
|
|
- (= (nth value (dec pos)) sym)
|
|
|
- (if (> (count value) pos)
|
|
|
- (not= (nth value pos) sym)
|
|
|
- true)))
|
|
|
- (commands/simple-insert! input-id "^^" {:backward-pos 2})
|
|
|
-
|
|
|
- :else
|
|
|
- nil)))})
|
|
|
- (mixins/on-key-up
|
|
|
- state
|
|
|
- {}
|
|
|
- (fn [e key-code]
|
|
|
- (let [k (gobj/get e "key")
|
|
|
- format (:format (get-state state))]
|
|
|
- (when-not (state/get-editor-show-input)
|
|
|
- (when (and @*show-commands (not= key-code 191)) ; not /
|
|
|
- (let [matched-commands (editor-handler/get-matched-commands input)]
|
|
|
- (if (seq matched-commands)
|
|
|
- (do
|
|
|
- (reset! *show-commands true)
|
|
|
- (reset! *matched-commands matched-commands))
|
|
|
- (reset! *show-commands false))))
|
|
|
- (when (and @*show-block-commands (not= key-code 188)) ; not <
|
|
|
- (let [matched-block-commands (editor-handler/get-matched-block-commands input)]
|
|
|
- (if (seq matched-block-commands)
|
|
|
- (cond
|
|
|
- (= key-code 9) ;tab
|
|
|
- (when @*show-block-commands
|
|
|
- (util/stop e)
|
|
|
- (editor-handler/insert-command! input-id
|
|
|
- (last (first matched-block-commands))
|
|
|
- format
|
|
|
- {:last-pattern commands/angle-bracket}))
|
|
|
-
|
|
|
- :else
|
|
|
- (reset! *matched-block-commands matched-block-commands))
|
|
|
- (reset! *show-block-commands false))))
|
|
|
- (editor-handler/close-autocomplete-if-outside input))))))))
|
|
|
- {:did-mount (fn [state]
|
|
|
- (let [[{:keys [dummy? format block-parent-id]} id] (:rum/args state)
|
|
|
- content (get-in @state/state [:editor/content id])
|
|
|
- input (gdom/getElement id)]
|
|
|
- (when block-parent-id
|
|
|
- (state/set-editing-block-dom-id! block-parent-id))
|
|
|
- (if (= :indent-outdent (state/get-editor-op))
|
|
|
- (when input
|
|
|
- (when-let [pos (state/get-edit-pos)]
|
|
|
- (util/set-caret-pos! input pos)))
|
|
|
- (editor-handler/restore-cursor-pos! id content dummy?))
|
|
|
-
|
|
|
- (when input
|
|
|
- (dnd/subscribe!
|
|
|
- input
|
|
|
- :upload-images
|
|
|
- {:drop (fn [e files]
|
|
|
- (editor-handler/upload-image id files format editor-handler/*image-uploading? true))}))
|
|
|
-
|
|
|
- ;; Here we delay this listener, otherwise the click to edit event will trigger a outside click event,
|
|
|
- ;; which will hide the editor so no way for editing.
|
|
|
- (js/setTimeout #(keyboards-handler/esc-save! state) 100)
|
|
|
-
|
|
|
- (when-let [element (gdom/getElement id)]
|
|
|
- (.focus element)))
|
|
|
- state)
|
|
|
- :did-remount (fn [_old-state state]
|
|
|
- (keyboards-handler/esc-save! state)
|
|
|
- state)
|
|
|
- :will-unmount (fn [state]
|
|
|
- (let [{:keys [id value format block repo dummy? config]} (get-state state)
|
|
|
- file? (:file? config)]
|
|
|
- (when-let [input (gdom/getElement id)]
|
|
|
- ;; (.removeEventListener input "paste" (fn [event]
|
|
|
- ;; (append-paste-doc! format event)))
|
|
|
- (let [s (str "cljs-drag-n-drop." :upload-images)
|
|
|
- a (gobj/get input s)
|
|
|
- timer (:timer a)]
|
|
|
-
|
|
|
- (and timer
|
|
|
- (dnd/unsubscribe!
|
|
|
- input
|
|
|
- :upload-images))))
|
|
|
- (editor-handler/clear-when-saved!)
|
|
|
- (if file?
|
|
|
- (let [path (:file-path config)
|
|
|
- content (db/get-file-no-sub path)
|
|
|
- value (some-> (gdom/getElement path)
|
|
|
- (gobj/get "value"))]
|
|
|
- (when (and
|
|
|
- (not (string/blank? value))
|
|
|
- (not= (string/trim value) (string/trim content)))
|
|
|
- (let [old-page-name (db/get-file-page path false)]
|
|
|
- (page-handler/rename-when-alter-title-property! old-page-name path format content value)
|
|
|
- (file/alter-file (state/get-current-repo) path (string/trim value)
|
|
|
- {:re-render-root? true}))))
|
|
|
- (when-not (contains? #{:insert :indent-outdent :auto-save} (state/get-editor-op))
|
|
|
- (editor-handler/save-block! (get-state state) value))))
|
|
|
- state)}
|
|
|
+(rum/defcs box < rum/reactive
|
|
|
+ (mixins/event-mixin setup-key-listener!)
|
|
|
+ lifecycle/lifecycle
|
|
|
[state {:keys [on-hide dummy? node format block block-parent-id]
|
|
|
- :or {dummy? false}
|
|
|
- :as option} id config]
|
|
|
+ :or {dummy? false}
|
|
|
+ :as option} id config]
|
|
|
(let [content (state/get-edit-content)]
|
|
|
[:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
|
|
|
(when config/mobile? (mobile-bar state id))
|
|
|
(ui/ls-textarea
|
|
|
{:id id
|
|
|
+ :class "mousetrap"
|
|
|
:cacheMeasurements true
|
|
|
:default-value (or content "")
|
|
|
:minRows (if (state/enable-grammarly?) 2 1)
|
|
|
- :on-click (fn [_e]
|
|
|
- (let [input (gdom/getElement id)
|
|
|
- current-pos (:pos (util/get-caret-pos input))]
|
|
|
- (state/set-edit-pos! current-pos)
|
|
|
- (editor-handler/close-autocomplete-if-outside input)))
|
|
|
- :on-change (fn [e]
|
|
|
- (let [value (util/evalue e)
|
|
|
- current-pos (:pos (util/get-caret-pos (gdom/getElement id)))]
|
|
|
- (state/set-edit-content! id value false)
|
|
|
- (state/set-edit-pos! current-pos)
|
|
|
- (when-let [repo (or (:block/repo block)
|
|
|
- (state/get-current-repo))]
|
|
|
- (state/set-editor-last-input-time! repo (util/time-ms))
|
|
|
- (db/clear-repo-persistent-job! repo))
|
|
|
- (let [input (gdom/getElement id)
|
|
|
- native-e (gobj/get e "nativeEvent")
|
|
|
- last-input-char (util/nth-safe value (dec current-pos))]
|
|
|
- (case last-input-char
|
|
|
- "/"
|
|
|
- ;; TODO: is it cross-browser compatible?
|
|
|
- (when (not= (gobj/get native-e "inputType") "insertFromPaste")
|
|
|
- (when-let [matched-commands (seq (editor-handler/get-matched-commands input))]
|
|
|
- (reset! *slash-caret-pos (util/get-caret-pos input))
|
|
|
- (reset! *show-commands true)))
|
|
|
- "<"
|
|
|
- (when-let [matched-commands (seq (editor-handler/get-matched-block-commands input))]
|
|
|
- (reset! *angle-bracket-caret-pos (util/get-caret-pos input))
|
|
|
- (reset! *show-block-commands true))
|
|
|
- nil))))
|
|
|
- :on-paste (fn [e]
|
|
|
- (when-let [handled
|
|
|
- (let [pick-one-allowed-item
|
|
|
- (fn [items]
|
|
|
- (when (and items (.-length items))
|
|
|
- (let [files (. (js/Array.from items) (filter #(= (.-kind %) "file")))
|
|
|
- it (gobj/get files 0) ;;; TODO: support multiple files
|
|
|
- mime (and it (.-type it))]
|
|
|
- (cond
|
|
|
- (contains? #{"image/jpeg" "image/png" "image/jpg" "image/gif"} mime) [:image (. it getAsFile)]))))
|
|
|
- clipboard-data (gobj/get e "clipboardData")
|
|
|
- items (or (.-items clipboard-data)
|
|
|
- (.-files clipboard-data))
|
|
|
- picked (pick-one-allowed-item items)]
|
|
|
- (when (and picked (get picked 1))
|
|
|
- (match picked
|
|
|
- [:image file] (editor-handler/set-image-pending-file file))
|
|
|
- true))]
|
|
|
- (util/stop e)))
|
|
|
+ :on-click (editor-handler/editor-on-click! id)
|
|
|
+ :on-change (editor-handler/editor-on-change! block id search-timeout)
|
|
|
+ :on-paste (editor-handler/editor-on-paste! id)
|
|
|
:auto-focus false})
|
|
|
|
|
|
;; TODO: how to render the transitions asynchronously?
|