| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- (ns electron.core
- (:require [electron.handler :as handler]
- [electron.search :as search]
- [electron.updater :refer [init-updater]]
- [electron.utils :refer [*win mac? win32? linux? prod? dev? logger open]]
- [electron.configs :as cfgs]
- [clojure.string :as string]
- [promesa.core :as p]
- [cljs-bean.core :as bean]
- [electron.fs-watcher :as fs-watcher]
- ["fs-extra" :as fs]
- ["path" :as path]
- ["os" :as os]
- ["electron" :refer [BrowserWindow app protocol ipcMain dialog Menu MenuItem session] :as electron]
- ["electron-window-state" :as windowStateKeeper]
- [clojure.core.async :as async]
- [electron.state :as state]
- [electron.git :as git]
- ["/electron/utils" :as utils]))
- (defonce LSP_SCHEME "lsp")
- (defonce LSP_PROTOCOL (str LSP_SCHEME "://"))
- (defonce PLUGIN_URL (str LSP_PROTOCOL "logseq.io/"))
- (defonce STATIC_URL (str LSP_PROTOCOL "logseq.com/"))
- (defonce PLUGINS_ROOT (.join path (.homedir os) ".logseq/plugins"))
- (def ROOT_PATH (path/join js/__dirname ".."))
- (def MAIN_WINDOW_ENTRY (if dev?
- ;;"http://localhost:3001"
- (str "file://" (path/join js/__dirname "index.html"))
- (str "file://" (path/join js/__dirname "electron.html"))))
- (defonce *setup-fn (volatile! nil))
- (defonce *teardown-fn (volatile! nil))
- (defonce *quit-dirty? (volatile! true))
- ;; Handle creating/removing shortcuts on Windows when installing/uninstalling.
- (when (js/require "electron-squirrel-startup") (.quit app))
- (defn create-main-window
- "Creates main app window"
- []
- (let [win-state (windowStateKeeper (clj->js {:defaultWidth 980 :defaultHeight 700}))
- win-opts (cond->
- {:width (.-width win-state)
- :height (.-height win-state)
- :frame true
- :titleBarStyle "hiddenInset"
- :trafficLightPosition {:x 16 :y 16}
- :autoHideMenuBar (not mac?)
- :webPreferences
- {:plugins true ; pdf
- :nodeIntegration false
- :nodeIntegrationInWorker false
- :webSecurity (not dev?)
- :contextIsolation true
- :spellcheck ((fnil identity true) (cfgs/get-item :spell-check))
- ;; Remove OverlayScrollbars and transition `.scrollbar-spacing`
- ;; to use `scollbar-gutter` after the feature is implemented in browsers.
- :enableBlinkFeatures 'OverlayScrollbars'
- :preload (path/join js/__dirname "js/preload.js")}}
- linux?
- (assoc :icon (path/join js/__dirname "icons/logseq.png")))
- url MAIN_WINDOW_ENTRY
- win (BrowserWindow. (clj->js win-opts))]
- (.manage win-state win)
- (.onBeforeSendHeaders (.. session -defaultSession -webRequest)
- (clj->js {:urls (array "*://*.youtube.com/*")})
- (fn [^js details callback]
- (let [url (.-url details)
- urlObj (js/URL. url)
- origin (.-origin urlObj)
- requestHeaders (.-requestHeaders details)]
- (if (and
- (.hasOwnProperty requestHeaders "referer")
- (not-empty (.-referer requestHeaders)))
- (callback #js {:cancel false
- :requestHeaders requestHeaders})
- (do
- (set! (.-referer requestHeaders) origin)
- (callback #js {:cancel false
- :requestHeaders requestHeaders}))))))
- (.loadURL win url)
- ;;(when dev? (.. win -webContents (openDevTools)))
- win))
- (defn setup-updater! [^js win]
- ;; manual/auto updater
- (when-not linux?
- (init-updater {:repo "logseq/logseq"
- :logger logger
- :win win})))
- (defn setup-interceptor! []
- (.registerFileProtocol
- protocol "assets"
- (fn [^js request callback]
- (let [url (.-url request)
- path (string/replace url "assets://" "")
- path (js/decodeURIComponent path)]
- (callback #js {:path path}))))
- (.registerFileProtocol
- protocol LSP_SCHEME
- (fn [^js request callback]
- (let [url (.-url request)
- url' ^js (js/URL. url)
- [_ ROOT] (if (string/starts-with? url PLUGIN_URL)
- [PLUGIN_URL PLUGINS_ROOT]
- [STATIC_URL js/__dirname])
- path' (.-pathname url')
- path' (js/decodeURIComponent path')
- path' (.join path ROOT path')]
- (callback #js {:path path'}))))
- #(do
- (.unregisterProtocol protocol LSP_SCHEME)
- (.unregisterProtocol protocol "assets")))
- (defn- handle-export-publish-assets [_event html custom-css-path repo-path asset-filenames]
- (p/let [app-path (. app getAppPath)
- asset-filenames (js->clj asset-filenames)
- result (. dialog showOpenDialog (clj->js {:properties ["openDirectory" "createDirectory" "promptToCreate", "multiSelections"]}))
- result (get (js->clj result) "filePaths")
- root-dir (first result)]
- (when root-dir
- (let [static-dir (path/join root-dir "static")
- assets-from-dir (path/join repo-path "assets")
- assets-to-dir (path/join root-dir "assets")
- index-html-path (path/join root-dir "index.html")]
- (p/let [_ (. fs ensureDir static-dir)
- _ (. fs ensureDir assets-to-dir)
- _ (p/all (concat
- [(. fs writeFile index-html-path html)
- (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
- (map
- (fn [filename]
- (-> (. fs copy (path/join assets-from-dir filename) (path/join assets-to-dir filename))
- (p/catch
- (fn [e]
- (println (str "Failed to copy " (path/join assets-from-dir filename) " to " (path/join assets-to-dir filename)))
- (js/console.error e)))))
- asset-filenames)
- (map
- (fn [part]
- (. fs copy (path/join app-path part) (path/join static-dir part)))
- ["css" "fonts" "icons" "img" "js"])))
- custom-css (. fs readFile custom-css-path)
- _ (. fs writeFile (path/join static-dir "css" "custom.css") custom-css)
- js-files ["main.js" "code-editor.js" "excalidraw.js"]
- _ (p/all (map (fn [file]
- (. fs removeSync (path/join static-dir "js" file)))
- js-files))
- _ (p/all (map (fn [file]
- (. fs moveSync
- (path/join static-dir "js" "publishing" file)
- (path/join static-dir "js" file)))
- js-files))
- _ (. fs removeSync (path/join static-dir "js" "publishing"))
- ;; remove source map files
- ;; TODO: ugly, replace with ls-files and filter with ".map"
- _ (p/all (map (fn [file]
- (. fs removeSync (path/join static-dir "js" (str file ".map"))))
- ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
- (. dialog showMessageBox (clj->js {:message (str "Export public pages and publish assets to " root-dir " successfully")})))))))
- (defn setup-app-manager!
- [^js win]
- (let [toggle-win-channel "toggle-max-or-min-active-win"
- call-app-channel "call-application"
- call-win-channel "call-main-win"
- export-publish-assets "export-publish-assets"
- quit-dirty-state "set-quit-dirty-state"
- web-contents (. win -webContents)]
- (doto ipcMain
- (.handle quit-dirty-state
- (fn [_ dirty?]
- (vreset! *quit-dirty? (boolean dirty?))))
- (.handle toggle-win-channel
- (fn [_ toggle-min?]
- (when-let [active-win (.getFocusedWindow BrowserWindow)]
- (if toggle-min?
- (if (.isMinimized active-win)
- (.restore active-win)
- (.minimize active-win))
- (if (.isMaximized active-win)
- (.unmaximize active-win)
- (.maximize active-win))))))
- (.handle export-publish-assets handle-export-publish-assets)
- (.handle call-app-channel
- (fn [_ type & args]
- (try
- (js-invoke app type args)
- (catch js/Error e
- (js/console.error e)))))
- (.handle call-win-channel
- (fn [_ type & args]
- (try
- (js-invoke @*win type args)
- (catch js/Error e
- (js/console.error e))))))
- (.on web-contents "context-menu"
- (fn
- [_event params]
- (let [menu (Menu.)
- suggestions (.-dictionarySuggestions ^js params)]
- (doseq [suggestion suggestions]
- (. menu append
- (MenuItem. (clj->js {:label
- suggestion
- :click
- (fn [] (. web-contents replaceMisspelling suggestion))}))))
- (when-let [misspelled-word (.-misspelledWord ^js params)]
- (. menu append
- (MenuItem. (clj->js {:label
- "Add to dictionary"
- :click
- (fn [] (.. web-contents -session (addWordToSpellCheckerDictionary misspelled-word)))}))))
- (. menu popup))))
- (.on web-contents "new-window"
- (fn [e url]
- (let [url (if (string/starts-with? url "file:")
- (js/decodeURIComponent url) url)
- url (if-not win32? (string/replace url "file://" "") url)]
- (.. logger (info "new-window" url))
- (if (string/includes?
- (.normalize path url)
- (.join path (. app getAppPath) "index.html"))
- (.info logger "pass-window" url)
- (open url)))
- (.preventDefault e)))
- (doto win
- (.on "enter-full-screen" #(.send web-contents "full-screen" "enter"))
- (.on "leave-full-screen" #(.send web-contents "full-screen" "leave")))
- #(do (.removeHandler ipcMain toggle-win-channel)
- (.removeHandler ipcMain export-publish-assets)
- (.removeHandler ipcMain quit-dirty-state)
- (.removeHandler ipcMain call-app-channel)
- (.removeHandler ipcMain call-win-channel))))
- (defn- destroy-window!
- [^js win]
- (.destroy win))
- (defn main
- []
- (if-not (.requestSingleInstanceLock app)
- (do
- (search/close!)
- (.quit app))
- (do
- (.registerSchemesAsPrivileged
- protocol (bean/->js [{:scheme LSP_SCHEME
- :privileges {:standard true
- :secure true
- :bypassCSP true
- :supportFetchAPI true}}]))
- (.on app "second-instance"
- (fn [_event _commandLine _workingDirectory]
- (when-let [win @*win]
- (when (.isMinimized ^object win)
- (.restore win))
- (.focus win))))
- (.on app "window-all-closed" (fn []
- (try
- (fs-watcher/close-watcher!)
- (search/close!)
- (catch js/Error e
- (js/console.error e)))
- (.quit app)))
- (.on app "ready"
- (fn []
- (let [t0 (setup-interceptor!)
- ^js win (create-main-window)
- _ (reset! *win win)
- *quitting? (atom false)]
- (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
- (utils/disableXFrameOptions win)
- (when (search/version-changed?)
- (search/rm-search-dir!))
- (search/ensure-search-dir!)
- (search/open-dbs!)
- (git/auto-commit-current-graph!)
- (vreset! *setup-fn
- (fn []
- (let [t1 (setup-updater! win)
- t2 (setup-app-manager! win)
- tt (handler/set-ipc-handler! win)]
- (vreset! *teardown-fn
- #(doseq [f [t0 t1 t2 tt]]
- (and f (f)))))))
- ;; setup effects
- (@*setup-fn)
- ;; main window events
- (.on win "close" (fn [e]
- (when @*quit-dirty?
- (.preventDefault e)
- (let [web-contents (. win -webContents)]
- (.send web-contents "persistent-dbs"))
- (async/go
- (let [_ (async/<! state/persistent-dbs-chan)]
- (if (or @*quitting? (not mac?))
- (when-let [win @*win]
- (destroy-window! win)
- (reset! *win nil))
- (do (.preventDefault ^js/Event e)
- (if (and mac? (.isFullScreen win))
- (do (.once win "leave-full-screen" #(.hide win))
- (.setFullScreen win false))
- (.hide win)))))))))
- (.on app "before-quit" (fn [_e] (reset! *quitting? true)))
- (.on app "activate" #(if @*win (.show win)))))))))
- (defn start []
- (js/console.log "Main - start")
- (when @*setup-fn (@*setup-fn)))
- (defn stop []
- (js/console.log "Main - stop")
- (when @*teardown-fn (@*teardown-fn)))
|