improve(electron): compatible paste assets for multiple platform

pull/1179/head
charlie 2021-01-28 18:01:03 +08:00
parent 32c2d4f268
commit c151c9c7cf
3 changed files with 134 additions and 119 deletions

View File

@ -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
})

View File

@ -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?))
{: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))}))
(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)
;; 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)
(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})

View File

@ -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?)