From eee8fb6bb2d2f73cc57f17d762e898194ac437eb Mon Sep 17 00:00:00 2001 From: charlie Date: Thu, 31 Dec 2020 15:03:16 +0800 Subject: [PATCH] feat(editor): support paste an image in block --- src/main/frontend/components/editor.cljs | 95 +++++++++++++++-------- src/main/frontend/components/widgets.cljs | 2 +- src/main/frontend/handler/editor.cljs | 11 ++- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/main/frontend/components/editor.cljs b/src/main/frontend/components/editor.cljs index b8300074e..5d7fd4728 100644 --- a/src/main/frontend/components/editor.cljs +++ b/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 diff --git a/src/main/frontend/components/widgets.cljs b/src/main/frontend/components/widgets.cljs index 839124dd0..1d6d2c857 100644 --- a/src/main/frontend/components/widgets.cljs +++ b/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)))}]]]] diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index d964bd794..6b8660e16 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/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?)