Browse Source

feat(editor): support paste an image in block

charlie 4 years ago
parent
commit
eee8fb6bb2

+ 62 - 33
src/main/frontend/components/editor.cljs

@@ -20,6 +20,7 @@
             [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
@@ -360,6 +361,15 @@
        (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
+                                (fn [_ _ f0 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))))
+                   state)
+   :will-unmount (fn [state]
+                   (remove-watch editor-handler/*image-pending-file ::pending-image))}
   [id format]
   [:div.image-uploader
    [:input
@@ -693,40 +703,59 @@
     [:div.editor-inner {:class (if block "block-editor" "non-block-editor")}
      (when config/mobile? (mobile-bar state id))
      (ui/ls-textarea
-      {:id id
+      {:id                id
        :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))))
-       :auto-focus false})
+       :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)))
+       :auto-focus        false})
 
      ;; TODO: how to render the transitions asynchronously?
      (transition-cp

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

@@ -54,7 +54,7 @@
           [:div.mt-2.mb-4.relative.rounded-md.shadow-sm.max-w-xs
            [:input#branch.form-input.block.w-full.sm:text-sm.sm:leading-5
             {:value @branch
-             :placeholder "master"
+             :placeholder "e.g. master"
              :on-change (fn [e]
                           (reset! branch (util/evalue e)))}]]]]
 

+ 9 - 2
src/main/frontend/handler/editor.cljs

@@ -44,6 +44,7 @@
             [lambdaisland.glogi :as log]))
 
 ;; FIXME: should support multiple images concurrently uploading
+(defonce *image-pending-file (atom nil))
 (defonce *image-uploading? (atom false))
 (defonce *image-uploading-process (atom 0))
 (defonce *selected-text (atom nil))
@@ -1495,7 +1496,7 @@
     nil))
 
 (defn upload-image
-  [id files format uploading? drop?]
+  [id files format uploading? drop-or-paste?]
   (image/upload
    files
    (fn [file file-name file-type]
@@ -1506,16 +1507,21 @@
         (insert-command! id
                          (get-image-link format signed-url file-name)
                          format
-                         {:last-pattern (if drop? "" commands/slash)
+                         {:last-pattern (if drop-or-paste? "" commands/slash)
                           :restore? true})
 
+        (reset! *image-uploading? false)
         (reset! *image-uploading-process 0))
       (fn [e]
         (let [process (* (/ (gobj/get e "loaded")
                             (gobj/get e "total"))
                          100)]
+          (reset! *image-uploading? false)
           (reset! *image-uploading-process process)))))))
 
+(defn set-image-pending-file [file]
+  (reset! *image-pending-file file))
+
 ;; Editor should track some useful information, like editor modes.
 ;; For example:
 ;; 1. Which file format is it, markdown or org mode?
@@ -1662,6 +1668,7 @@
   [input]
   (or @*show-commands
       @*show-block-commands
+      @*image-uploading?
       (state/get-editor-show-input)
       (state/get-editor-show-page-search?)
       (state/get-editor-show-block-search?)