浏览代码

improve(electron): compatible paste assets for multiple platform

charlie 4 年之前
父节点
当前提交
c151c9c7cf
共有 3 个文件被更改,包括 137 次插入122 次删除
  1. 20 13
      resources/js/preload.js
  2. 97 89
      src/main/frontend/components/editor.cljs
  3. 20 20
      src/main/frontend/handler/editor.cljs

+ 20 - 13
resources/js/preload.js

@@ -5,6 +5,19 @@ const { ipcRenderer, contextBridge, shell, clipboard, BrowserWindow } = require(
 const IS_MAC = process.platform === 'darwin'
 const IS_WIN32 = process.platform === 'win32'
 
+function getFilePathFromClipboard () {
+  if (IS_WIN32) {
+    const rawFilePath = clipboard.read('FileNameW')
+    return rawFilePath.replace(new RegExp(String.fromCharCode(0), 'g'), '')
+  }
+
+  return clipboard.read('public.file-url').replace('file://', '')
+}
+
+function isClipboardHasImage () {
+  return !clipboard.readImage().isEmpty()
+}
+
 contextBridge.exposeInMainWorld('apis', {
   doAction: async (arg) => {
     return await ipcRenderer.invoke('main', arg)
@@ -65,11 +78,12 @@ contextBridge.exposeInMainWorld('apis', {
 
     await fs.promises.mkdir(assetsRoot, { recursive: true })
 
-    from = from || getFilePathFromClipboard()
+    from = decodeURIComponent(from || getFilePathFromClipboard())
 
     if (from) {
       // console.debug('copy file: ', from, dest)
-      return await fs.promises.copyFile(from, dest)
+      await fs.promises.copyFile(from, dest)
+      return path.basename(from)
     }
 
     // support image
@@ -83,16 +97,6 @@ contextBridge.exposeInMainWorld('apis', {
         nImg.toPNG()
       )
     }
-
-    // fns
-    function getFilePathFromClipboard () {
-      if (IS_WIN32) {
-        const rawFilePath = clipboard.read('FileNameW')
-        return rawFilePath.replace(new RegExp(String.fromCharCode(0), 'g'), '')
-      }
-
-      return clipboard.read('public.file-url').replace('file://', '')
-    }
   },
 
   toggleMaxOrMinActiveWindow (isToggleMin = false) {
@@ -107,5 +111,8 @@ contextBridge.exposeInMainWorld('apis', {
    */
   async _callApplication (type, ...args) {
     return await ipcRenderer.invoke('call-application', type, ...args)
-  }
+  },
+
+  getFilePathFromClipboard,
+  isClipboardHasImage
 })

+ 97 - 89
src/main/frontend/components/editor.cljs

@@ -58,7 +58,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 +71,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)}
@@ -108,7 +108,7 @@
                                                                    page-ref-text
                                                                    format
                                                                    {:last-pattern (str "[[" (if @editor-handler/*selected-text "" q))
-                                                                    :postfix-fn (fn [s] (util/replace-first "]]" s ""))}))))
+                                                                    :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))
@@ -128,9 +128,9 @@
           (ui/auto-complete
            matched-pages
            {:on-chosen chosen-handler
-            :on-enter non-exist-page-handler
+            :on-enter  non-exist-page-handler
             :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)}
@@ -156,7 +156,7 @@
                                                                  (util/format "((%s))" uuid-string)
                                                                  format
                                                                  {:last-pattern (str "((" (if @editor-handler/*selected-text "" q))
-                                                                  :postfix-fn (fn [s] (util/replace-first "))" s ""))})
+                                                                  :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)
@@ -170,12 +170,12 @@
                                         (util/cursor-move-forward input 2))]
           (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)}
@@ -223,12 +223,12 @@
                                   (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   chosen-handler
+            :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]
@@ -252,17 +252,17 @@
     {: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]))})}
     "(())"]])
 
 (rum/defcs input < rum/reactive
@@ -276,7 +276,7 @@
             (let [input-value (get state ::input-value)
                   input-option (get @state/state :editor/show-input)]
               (when (seq @input-value)
-                ;; no new line input
+                                   ;; no new line input
                 (util/stop e)
                 (let [[_id on-submit] (:rum/args state)
                       {:keys [pos]} @*slash-caret-pos
@@ -306,11 +306,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 +357,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-image 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-image 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
@@ -434,21 +434,21 @@
                             (profile
                              "Insert block"
                              (editor-handler/insert-new-block! state))))))))))
-         ;; up
+                          ;; 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
+                          ;; 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
+                          ;; backspace
          8  (fn [state e]
               (let [node (gdom/getElement input-id)
                     current-pos (:pos (util/get-caret-pos node))
@@ -464,7 +464,7 @@
                   nil
 
                   (and (zero? current-pos)
-                       ;; not the top block in a block page
+                                        ;; not the top block in a block page
                        (not (and page
                                  (util/uuid-string? page)
                                  (= (medley/uuid page) block-id))))
@@ -482,7 +482,7 @@
                     (reset! *angle-bracket-caret-pos nil)
                     (reset! *show-block-commands false))
 
-                  ;; pair
+                                   ;; pair
                   (and
                    deleted
                    (contains?
@@ -505,13 +505,13 @@
                       :else
                       nil))
 
-                  ;; deleting hashtag
+                                   ;; deleting hashtag
                   (and (= deleted "#") (state/get-editor-show-page-search-hashtag?))
                   (state/set-editor-show-page-search-hashtag! false)
 
                   :else
                   nil)))
-         ;; tab
+                          ;; tab
          9  (fn [state e]
               (let [input-id (state/get-edit-input-id)
                     input (and input-id (gdom/getElement id))
@@ -615,18 +615,18 @@
           (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 /
+              (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 <
+              (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
+                      (= key-code 9)       ;tab
                       (when @*show-block-commands
                         (util/stop e)
                         (editor-handler/insert-command! input-id
@@ -638,41 +638,41 @@
                       (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)
+  {: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/*asset-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)))
+                                      ;; (.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)]
@@ -698,8 +698,8 @@
                          (editor-handler/save-block! (get-state state) value))))
                    state)}
   [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))
@@ -741,20 +741,28 @@
                             (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)]))))
+                                               (if (util/electron?)
+
+                                                 (let [existed-file-path (js/window.apis.getFilePathFromClipboard)
+                                                       has-file-path? (not (string/blank? existed-file-path))
+                                                       has-image? (js/window.apis.isClipboardHasImage)]
+                                                   (if (or has-image? has-file-path?)
+                                                     [:asset (js/File. #js[] (if has-file-path? existed-file-path "image.png"))]))
+
+                                                 (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) [:asset (. 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))
+                                         (js/console.log (get picked 1))
+                                         (if (get picked 1)
                                            (match picked
-                                             [:image file] (editor-handler/set-image-pending-file file))
-                                           true))]
+                                             [:asset file] (editor-handler/set-asset-pending-file file))))]
                               (util/stop e)))
        :auto-focus        false})
 

+ 20 - 20
src/main/frontend/handler/editor.cljs

@@ -48,9 +48,9 @@
             [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 *asset-pending-file (atom nil))
+(defonce *asset-uploading? (atom false))
+(defonce *asset-uploading-process (atom 0))
 (defonce *selected-text (atom nil))
 
 (defn- get-selection-and-format
@@ -1561,7 +1561,7 @@
         (if (util/electron?)
           (let [from (.-path file)]
             (p/then (js/window.apis.copyFileToAssets dir filename from)
-                    #(p/resolved [filename file])))
+                    #(p/resolved [filename (if (string? %) (js/File. #js[] %) file)])))
           (p/then (fs/write-file! repo dir filename (.stream file) nil)
                   #(p/resolved [filename file]))))))))
 
@@ -1600,7 +1600,7 @@
                             (string/replace #"^assets://" ""))) nil))))
 
 (defn upload-image
-  [id files format uploading? drop-or-paste?]
+  [id ^js files format uploading? drop-or-paste?]
   (let [repo (state/get-current-repo)
         block (state/get-edit-block)]
     (if (config/local-db? repo)
@@ -1618,32 +1618,32 @@
           (p/finally
             (fn []
               (reset! uploading? false)
-              (reset! *image-uploading? false)
-              (reset! *image-uploading-process 0))))
+              (reset! *asset-uploading? false)
+              (reset! *asset-uploading-process 0))))
       (image/upload
-       files
-       (fn [file file-name file-type]
+        files
+        (fn [file file-name file-type]
          (image-handler/request-presigned-url
-          file file-name file-type
-          uploading?
-          (fn [signed-url]
+           file file-name file-type
+           uploading?
+           (fn [signed-url]
             (insert-command! id
                              (get-image-link format signed-url file-name)
                              format
                              {:last-pattern (if drop-or-paste? "" commands/slash)
                               :restore?     true})
 
-            (reset! *image-uploading? false)
-            (reset! *image-uploading-process 0))
-          (fn [e]
+            (reset! *asset-uploading? false)
+            (reset! *asset-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)))))))))
+              (reset! *asset-uploading? false)
+              (reset! *asset-uploading-process process)))))))))
 
-(defn set-image-pending-file [file]
-  (reset! *image-pending-file file))
+(defn set-asset-pending-file [file]
+  (reset! *asset-pending-file file))
 
 ;; Editor should track some useful information, like editor modes.
 ;; For example:
@@ -1791,7 +1791,7 @@
   [input]
   (or @*show-commands
       @*show-block-commands
-      @*image-uploading?
+      @*asset-uploading?
       (state/get-editor-show-input)
       (state/get-editor-show-page-search?)
       (state/get-editor-show-block-search?)