From b7619958ece0626f0663a27144a64fbdd798b36e Mon Sep 17 00:00:00 2001 From: charlie Date: Mon, 25 Jan 2021 14:33:50 +0800 Subject: [PATCH] improve(electron): adapt paste/drop asset for electron clipboard api --- resources/js/preload.js | 44 ++++++++++++++-- src/electron/electron/core.cljs | 17 +++++- src/electron/electron/handler.cljs | 4 +- src/main/frontend/handler/editor.cljs | 37 ++++++++------ src/main/frontend/handler/image.cljs | 74 ++++++++++++++------------- 5 files changed, 116 insertions(+), 60 deletions(-) diff --git a/resources/js/preload.js b/resources/js/preload.js index 43cad312d51..f4a2819d22c 100644 --- a/resources/js/preload.js +++ b/resources/js/preload.js @@ -1,6 +1,9 @@ const fs = require('fs') const path = require('path') -const { ipcRenderer, contextBridge, shell } = require('electron') +const { ipcRenderer, contextBridge, shell, clipboard } = require('electron') + +const IS_MAC = process.platform === 'darwin' +const IS_WIN32 = process.platform === 'win32' contextBridge.exposeInMainWorld('apis', { doAction: async (arg) => { @@ -32,8 +35,16 @@ contextBridge.exposeInMainWorld('apis', { await shell.openExternal(url, options) }, + /** + * When from is empty. The resource maybe from + * client paste or screenshoot. + * @param repoPathRoot + * @param to + * @param from? + * @returns {Promise} + */ async copyFileToAssets (repoPathRoot, to, from) { - if (fs.statSync(from).isDirectory()) { + if (from && fs.statSync(from).isDirectory()) { throw new Error('not support copy directory') } @@ -45,6 +56,33 @@ contextBridge.exposeInMainWorld('apis', { } await fs.promises.mkdir(assetsRoot, { recursive: true }) - await fs.promises.copyFile(from, dest) + + from = !from && getFilePathFromClipboard() + + if (from) { + // console.debug('copy file: ', from, dest) + return await fs.promises.copyFile(from, dest) + } + + // support image + const nImg = clipboard.readImage() + + if (nImg && !nImg.isEmpty()) { + const rawExt = path.extname(dest) + return await fs.promises.writeFile( + dest.replace(rawExt, '.png'), + 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://', '') + } } }) diff --git a/src/electron/electron/core.cljs b/src/electron/electron/core.cljs index 8c4b0a232aa..ea6ac880168 100644 --- a/src/electron/electron/core.cljs +++ b/src/electron/electron/core.cljs @@ -2,9 +2,10 @@ (:require [electron.handler :as handler] [electron.updater :refer [init-updater]] [electron.utils :refer [mac? win32? prod? dev? log]] + [clojure.string :as string] ["fs" :as fs] ["path" :as path] - ["electron" :refer [BrowserWindow app] :as electron])) + ["electron" :refer [BrowserWindow app protocol] :as electron])) (def ROOT_PATH (path/join js/__dirname "..")) (def MAIN_WINDOW_ENTRY (str "file://" (path/join js/__dirname (if dev? "dev.html" "index.html")))) @@ -12,6 +13,7 @@ (def ^:dynamic *setup-fn* nil) (def ^:dynamic *teardown-fn* nil) (def ^:dynamic *teardown-updater* nil) +(def ^:dynamic *teardown-interceptor* nil) ;; Handle creating/removing shortcuts on Windows when installing/uninstalling. (when (js/require "electron-squirrel-startup") (.quit app)) @@ -41,6 +43,15 @@ :logger log :win win}))) +(defn setup-interceptor! [] + (.registerFileProtocol + protocol "assets" + (fn [^js request callback] + (let [url (.-url request) + path (string/replace url "assets://" "")] + (callback #js {:path path})))) + (set! *teardown-interceptor* #(.unregisterProtocol protocol "assets"))) + (defn main [] (.on app "window-all-closed" #(when-not mac? (.quit app))) @@ -54,13 +65,15 @@ (fn [] ;; updater (setup-updater! win) + (setup-interceptor!) ;; handler (handler/set-ipc-handler! win) ;; teardown #(do - (when *teardown-updater* (*teardown-updater*))))) + (when *teardown-updater* (*teardown-updater*)) + (when *teardown-interceptor* (*teardown-interceptor*))))) ;; setup effects (*setup-fn*) diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index 66baccbb9ff..5abfe0a5a14 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -71,7 +71,7 @@ (last (string/split file #"\."))) (defonce file-watcher-chan "file-watcher") -(defn send-file-watcher! [win type payload] +(defn send-file-watcher! [^js win type payload] (.. win -webContents (send file-watcher-chan (bean/->js {:type type :payload payload})))) @@ -80,7 +80,7 @@ [win dir] (let [watcher (.watch watcher dir (clj->js - {:ignored #"^\." ; FIXME read .gitignore and other ignore paths + {:ignored #"(^\.|/assets/)" ; FIXME read .gitignore and other ignore paths ;; :ignoreInitial true :persistent true :awaitWriteFinish true}))] diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index eaab5d00b58..b091f944ee1 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1570,10 +1570,8 @@ (js/console.debug "Write asset #" dir filename file) (if (util/electron?) (let [from (.-path file)] - (if (string/blank? from) - (throw (js/Error. "TODO: can not resolved From file path")) - (p/then (js/window.apis.copyFileToAssets dir filename from) - #(p/resolved [filename file])))) + (p/then (js/window.apis.copyFileToAssets dir filename from) + #(p/resolved [filename file]))) (p/then (fs/write-file! repo dir filename (.stream file) nil) #(p/resolved [filename file])))))))) @@ -1582,17 +1580,19 @@ (defn make-asset-url [path] ;; path start with "/assets" or compatible for "../assets" (let [repo-dir (config/get-repo-dir (state/get-current-repo)) - path (string/replace path "../" "/") - handle-path (str "handle" repo-dir path) - cached-url (get @*assets-url-cache (keyword handle-path))] - (if cached-url - (p/resolved cached-url) - (p/let [handle (frontend.idb/get-item handle-path) - file (and handle (.getFile handle))] - (when file - (p/let [url (js/URL.createObjectURL file)] - (swap! *assets-url-cache assoc (keyword handle-path) url) - url)))))) + path (string/replace path "../" "/")] + (if (util/electron?) + (str "assets://" repo-dir path) + (let [handle-path (str "handle" repo-dir path) + cached-url (get @*assets-url-cache (keyword handle-path))] + (if cached-url + (p/resolved cached-url) + (p/let [handle (frontend.idb/get-item handle-path) + file (and handle (.getFile handle))] + (when file + (p/let [url (js/URL.createObjectURL file)] + (swap! *assets-url-cache assoc (keyword handle-path) url) + url)))))))) (defn delete-asset-of-block! [{:keys [repo href title full-text block-id local?] :as opts}] @@ -1604,7 +1604,10 @@ (save-block! repo block content) (when local? ;; FIXME: should be relative to current block page path - (fs/unlink! (config/get-repo-path repo (string/replace href #"^../" "/")) nil)))) + (fs/unlink! (config/get-repo-path + repo (-> href + (string/replace #"^../" "/") + (string/replace #"^assets://" ""))) nil)))) (defn upload-image [id files format uploading? drop-or-paste?] @@ -1617,7 +1620,7 @@ (when-let [[url file] (and (seq res) (first res))] (insert-command! id - (get-image-link format (get-asset-link url) (.-name file)) + (get-image-link format (get-asset-link url) (if file (.-name file) "image")) format {:last-pattern (if drop-or-paste? "" commands/slash) :restore? true})))) diff --git a/src/main/frontend/handler/image.cljs b/src/main/frontend/handler/image.cljs index 3532190abe9..1027ecc032e 100644 --- a/src/main/frontend/handler/image.cljs +++ b/src/main/frontend/handler/image.cljs @@ -11,42 +11,44 @@ (defn render-local-images! [] - (try - (let [images (array-seq (gdom/getElementsByTagName "img")) - get-src (fn [image] (.getAttribute image "src")) - local-images (filter - (fn [image] - (let [src (get-src image)] - (and src - (not (or (util/starts-with? src "http://") - (util/starts-with? src "https://") - (util/starts-with? src "blob:")))))) - images)] - (doseq [img local-images] - (gobj/set img - "onerror" - (fn [] - (gobj/set (gobj/get img "style") - "display" "none"))) - (let [path (get-src img) - path (string/replace-first path "file:" "") - path (if (= (first path) \.) - (subs path 1) - path)] - (util/p-handle - (fs/read-file (config/get-repo-dir (state/get-current-repo)) - path) - (fn [blob] - (let [blob (js/Blob. (array blob) (clj->js {:type "image"})) - img-url (image/create-object-url blob)] - (gobj/set img "src" img-url) - (gobj/set (gobj/get img "style") - "display" "initial"))) - (fn [error] - (println "Can't read local image file: ") - (js/console.dir error)))))) - (catch js/Error e - nil))) + (when-not (and (util/electron?) + (config/local-db? (state/get-current-repo))) + (try + (let [images (array-seq (gdom/getElementsByTagName "img")) + get-src (fn [image] (.getAttribute image "src")) + local-images (filter + (fn [image] + (let [src (get-src image)] + (and src + (not (or (util/starts-with? src "http://") + (util/starts-with? src "https://") + (util/starts-with? src "blob:")))))) + images)] + (doseq [img local-images] + (gobj/set img + "onerror" + (fn [] + (gobj/set (gobj/get img "style") + "display" "none"))) + (let [path (get-src img) + path (string/replace-first path "file:" "") + path (if (= (first path) \.) + (subs path 1) + path)] + (util/p-handle + (fs/read-file (config/get-repo-dir (state/get-current-repo)) + path) + (fn [blob] + (let [blob (js/Blob. (array blob) (clj->js {:type "image"})) + img-url (image/create-object-url blob)] + (gobj/set img "src" img-url) + (gobj/set (gobj/get img "style") + "display" "initial"))) + (fn [error] + (println "Can't read local image file: ") + (js/console.dir error)))))) + (catch js/Error e + nil)))) (defn request-presigned-url [file filename mime-type uploading? url-handler on-processing]